mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 00:54:06 -08:00
210 lines
6.7 KiB
TypeScript
210 lines
6.7 KiB
TypeScript
import { nanoid } from 'nanoid';
|
||
|
||
import { NDV } from '../pages/ndv';
|
||
import { successToast } from '../pages/notifications';
|
||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||
|
||
const WorkflowPage = new WorkflowPageClass();
|
||
const ndv = new NDV();
|
||
|
||
describe('Code node', () => {
|
||
describe('Code editor', () => {
|
||
beforeEach(() => {
|
||
WorkflowPage.actions.visit();
|
||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||
WorkflowPage.actions.addNodeToCanvas('Code', true, true);
|
||
});
|
||
|
||
it('should show correct placeholders switching modes', () => {
|
||
cy.contains('// Loop over input items and add a new field').should('be.visible');
|
||
|
||
ndv.getters.parameterInput('mode').click();
|
||
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
|
||
|
||
cy.contains("// Add a new field called 'myNewField'").should('be.visible');
|
||
|
||
ndv.getters.parameterInput('mode').click();
|
||
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for All Items');
|
||
cy.contains('// Loop over input items and add a new field').should('be.visible');
|
||
});
|
||
|
||
it('should execute the placeholder successfully in both modes', () => {
|
||
ndv.actions.execute();
|
||
|
||
successToast().contains('Node executed successfully');
|
||
ndv.getters.parameterInput('mode').click();
|
||
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
|
||
|
||
ndv.actions.execute();
|
||
|
||
successToast().contains('Node executed successfully');
|
||
});
|
||
|
||
it('should show lint errors in `runOnceForAllItems` mode', () => {
|
||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
||
|
||
getEditor()
|
||
.type('{selectall}')
|
||
.paste(`$input.itemMatching()
|
||
$input.item
|
||
$('When clicking ‘Test workflow’').item
|
||
$input.first(1)
|
||
|
||
for (const item of $input.all()) {
|
||
item.foo
|
||
}
|
||
|
||
return
|
||
`);
|
||
getParameter().get('.cm-lint-marker-error').should('have.length', 6);
|
||
getParameter().contains('itemMatching').realHover();
|
||
cy.get('.cm-tooltip-lint').should(
|
||
'have.text',
|
||
'`.itemMatching()` expects an item index to be passed in as its argument.',
|
||
);
|
||
});
|
||
|
||
it('should show lint errors in `runOnceForEachItem` mode', () => {
|
||
const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible');
|
||
const getEditor = () => getParameter().find('.cm-content').should('exist');
|
||
|
||
ndv.getters.parameterInput('mode').click();
|
||
ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');
|
||
getEditor()
|
||
.type('{selectall}')
|
||
.paste(`$input.itemMatching()
|
||
$input.all()
|
||
$input.first()
|
||
$input.item()
|
||
|
||
return []
|
||
`);
|
||
|
||
getParameter().get('.cm-lint-marker-error').should('have.length', 5);
|
||
getParameter().contains('all').realHover();
|
||
cy.get('.cm-tooltip-lint').should(
|
||
'have.text',
|
||
"Method `$input.all()` is only available in the 'Run Once for All Items' mode.",
|
||
);
|
||
});
|
||
});
|
||
|
||
describe('Ask AI', () => {
|
||
it('tab should display based on experiment', () => {
|
||
WorkflowPage.actions.visit();
|
||
cy.window().then((win) => {
|
||
win.featureFlags.override('011_ask_AI', 'control');
|
||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||
WorkflowPage.actions.addNodeToCanvas('Code');
|
||
WorkflowPage.actions.openNode('Code');
|
||
|
||
cy.getByTestId('code-node-tab-ai').should('not.exist');
|
||
|
||
ndv.actions.close();
|
||
win.featureFlags.override('011_ask_AI', undefined);
|
||
WorkflowPage.actions.openNode('Code');
|
||
cy.getByTestId('code-node-tab-ai').should('not.exist');
|
||
});
|
||
});
|
||
|
||
describe('Enabled', () => {
|
||
beforeEach(() => {
|
||
WorkflowPage.actions.visit();
|
||
cy.window().then((win) => {
|
||
win.featureFlags.override('011_ask_AI', 'gpt3');
|
||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||
WorkflowPage.actions.addNodeToCanvas('Code', true, true);
|
||
});
|
||
});
|
||
|
||
it('tab should exist if experiment selected and be selectable', () => {
|
||
cy.getByTestId('code-node-tab-ai').should('exist');
|
||
cy.get('#tab-ask-ai').click();
|
||
cy.contains('Hey AI, generate JavaScript').should('exist');
|
||
});
|
||
|
||
it('generate code button should have correct state & tooltips', () => {
|
||
cy.getByTestId('code-node-tab-ai').should('exist');
|
||
cy.get('#tab-ask-ai').click();
|
||
|
||
cy.getByTestId('ask-ai-cta').should('be.disabled');
|
||
cy.getByTestId('ask-ai-cta').realHover();
|
||
cy.getByTestId('ask-ai-cta-tooltip-no-input-data').should('exist');
|
||
ndv.actions.executePrevious();
|
||
cy.getByTestId('ask-ai-cta').realHover();
|
||
cy.getByTestId('ask-ai-cta-tooltip-no-prompt').should('exist');
|
||
cy.getByTestId('ask-ai-prompt-input')
|
||
// Type random 14 character string
|
||
.type(nanoid(14));
|
||
|
||
cy.getByTestId('ask-ai-cta').realHover();
|
||
cy.getByTestId('ask-ai-cta-tooltip-prompt-too-short').should('exist');
|
||
|
||
cy.getByTestId('ask-ai-prompt-input')
|
||
.clear()
|
||
// Type random 15 character string
|
||
.type(nanoid(15));
|
||
cy.getByTestId('ask-ai-cta').should('be.enabled');
|
||
|
||
cy.getByTestId('ask-ai-prompt-counter').should('contain.text', '15 / 600');
|
||
});
|
||
|
||
it('should send correct schema and replace code', () => {
|
||
const prompt = nanoid(20);
|
||
cy.get('#tab-ask-ai').click();
|
||
ndv.actions.executePrevious();
|
||
|
||
cy.getByTestId('ask-ai-prompt-input').type(prompt);
|
||
|
||
cy.intercept('POST', '/rest/ask-ai', {
|
||
statusCode: 200,
|
||
body: {
|
||
data: {
|
||
code: 'console.log("Hello World")',
|
||
},
|
||
},
|
||
}).as('ask-ai');
|
||
|
||
cy.getByTestId('ask-ai-cta').click();
|
||
const askAiReq = cy.wait('@ask-ai');
|
||
|
||
askAiReq
|
||
.its('request.body')
|
||
.should('have.keys', ['question', 'model', 'context', 'n8nVersion']);
|
||
|
||
askAiReq.its('context').should('have.keys', ['schema', 'ndvPushRef', 'pushRef']);
|
||
|
||
cy.contains('Code generation completed').should('be.visible');
|
||
cy.getByTestId('code-node-tab-code').should('contain.text', 'console.log("Hello World")');
|
||
cy.get('#tab-code').should('have.class', 'is-active');
|
||
});
|
||
|
||
it('should show error based on status code', () => {
|
||
const prompt = nanoid(20);
|
||
cy.get('#tab-ask-ai').click();
|
||
ndv.actions.executePrevious();
|
||
|
||
cy.getByTestId('ask-ai-prompt-input').type(prompt);
|
||
|
||
const handledCodes = [
|
||
{ code: 400, message: 'Code generation failed due to an unknown reason' },
|
||
{ code: 413, message: 'Your workflow data is too large for AI to process' },
|
||
{ code: 429, message: "We've hit our rate limit with our AI partner" },
|
||
{ code: 500, message: 'Code generation failed due to an unknown reason' },
|
||
];
|
||
|
||
handledCodes.forEach(({ code, message }) => {
|
||
cy.intercept('POST', '/rest/ask-ai', {
|
||
statusCode: code,
|
||
status: code,
|
||
}).as('ask-ai');
|
||
|
||
cy.getByTestId('ask-ai-cta').click();
|
||
cy.contains(message).should('be.visible');
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|