Skip to content

Commit

Permalink
INT-3303: Update Vue integration tests to use BDD style. (#434)
Browse files Browse the repository at this point in the history
* INT-3303: Convert existing test.ts files to use bdd-styling conventions.

* create TestHelper.ts to isolate variables
update imports

* remove async from cFakeType function.

* Add test to LoadTinyTest.ts for loading from scriptSrc and Cloud.

* INT-3303: Convert cFakeType to asynchronous.

* INT-3303: Refactor InitTest.ts and related files.

* add loop to iterate each editor version for tests.

* afterEach function was added inside the loop for different versions.

* INT-3303: Fix defaultValue warning.
update cleanupTinymce to cleanupGlobalTinymce to be more specific.
fix spacing between tests.
add afterEach to context function.

* INT-3303: add remove() tinymce instance back to LoadTinyTests.

* Refactor EditorLoadTest.ts and related files

* Removed EditorLoadTest.ts

* Update src/stories/Editor.stories.tsx

Co-authored-by: tiny-ben-tran <[email protected]>

* Update src/test/ts/browser/InitTest.ts

Co-authored-by: tiny-ben-tran <[email protected]>

* Refactor event name validation tests in UtilsTest, included comments.
Removed Waiter from tests.
Wrapped various func into beforeEach() to improve readability.

* INT-3303: refine type definitions and standardize array iteration methods.

* Update src/test/ts/alien/TestHelper.ts

---------

Co-authored-by: tiny-ben-tran <[email protected]>
  • Loading branch information
kemister85 and tiny-ben-tran authored Oct 21, 2024
1 parent 28169fa commit ccc0099
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 210 deletions.
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"cSpell.words": [
"asynctest"
],
"typescript.tsdk": "node_modules/typescript/lib"
}
3 changes: 1 addition & 2 deletions src/stories/Editor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default {
table: {
defaultValue: {summary: '5'}
},
defaultValue: ['7'],
defaultValue: '7',
options: ['5', '5-dev', '5-testing', '6-testing', '6-stable', '7-dev', '7-testing', '7-stable'],
control: { type: 'select'}
},
Expand All @@ -79,7 +79,6 @@ export const Iframe: Story = (args) => ({
});
const cc = args.channel || lastChannel;
const conf = getConf(args.conf);
console.log('conf: ', conf);
return {
apiKey,
content,
Expand Down
67 changes: 33 additions & 34 deletions src/test/ts/alien/Loader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Chain } from '@ephox/agar';
import { Fun } from '@ephox/katamari';
import { Attribute, SugarBody, SugarElement, Insert, Remove, SelectorFind } from '@ephox/sugar';
import Editor from 'src/main/ts/index';
Expand All @@ -19,40 +18,40 @@ const getRoot = () => SelectorFind.descendant(SugarBody.body(), '#root').getOrTh
return root;
});

const cRender = (data: Record<string, any> = {}, template: string = `<editor :init="init" ></editor>`) =>
Chain.async<Context, Context>((_value, next, _die) => {
const root = getRoot();
const mountPoint = SugarElement.fromTag('div');
Insert.append(root, mountPoint);

const originalInit = data.init || {};
const originalSetup = originalInit.setup || Fun.noop;

const vm = createApp({
template,
components: {
Editor
},
data: () => ({
...data,
outputFormat: 'text',
init: {
...originalInit,
setup: (editor: any) => {
originalSetup(editor);
editor.on('SkinLoaded', () => {
setTimeout(() => {
next({ editor, vm });
}, 0);
});
}
// eslint-disable-next-line max-len
const pRender = (data: Record<string, any> = {}, template: string = `<editor :init="init"></editor>`): Promise<Record<string, any>> => new Promise((resolve) => {
const root = getRoot();
const mountPoint = SugarElement.fromTag('div');
Insert.append(root, mountPoint);

const originalInit = data.init || {};
const originalSetup = originalInit.setup || Fun.noop;

const vm = createApp({
template,
components: {
Editor
},
data: () => ({
...data,
outputFormat: 'text',
init: {
...originalInit,
setup: (editor: any) => {
originalSetup(editor);
editor.on('SkinLoaded', () => {
setTimeout(() => {
resolve({ editor, vm });
}, 0);
});
}
}),
}).mount(mountPoint.dom);
});
}
}),
}).mount(mountPoint.dom);
});

const cRemove = Chain.op(() => {
const remove = () => {
Remove.remove(getRoot());
});
};

export { cRender, cRemove };
export { pRender, remove, getRoot };
27 changes: 27 additions & 0 deletions src/test/ts/alien/TestHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Global, Strings, Arr } from '@ephox/katamari';
import { SugarElement, Attribute, SelectorFilter, Remove } from '@ephox/sugar';
import { ScriptLoader } from '../../../main/ts/ScriptLoader';

const VALID_API_KEY = 'qagffr3pkuv17a8on1afax661irst1hbr4e6tbv888sz91jc';

// Function to clean up and remove TinyMCE-related scripts and links from the document
const cleanupGlobalTinymce = () => {
ScriptLoader.reinitialize();
// This deletes global references to TinyMCE, to ensure a clean slate for each initialization when tests are switching to a different editor versions.
delete Global.tinymce;
delete Global.tinyMCE;
// Helper function to check if an element has a TinyMCE-related URI in a specific attribute
const hasTinymceUri = (attrName: string) => (elm: SugarElement<Element>) =>
Attribute.getOpt(elm, attrName).exists((src) => Strings.contains(src, 'tinymce'));
// Find all script and link elements that have a TinyMCE-related URI
const elements = Arr.flatten([
Arr.filter(SelectorFilter.all('script'), hasTinymceUri('src')),
Arr.filter(SelectorFilter.all('link'), hasTinymceUri('href')),
]);
Arr.each(elements, Remove.remove);
};

export {
VALID_API_KEY,
cleanupGlobalTinymce,
};
30 changes: 23 additions & 7 deletions src/test/ts/atomic/UtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { Assertions } from '@ephox/agar';
import { UnitTest } from '@ephox/bedrock-client';
import { describe, it } from '@ephox/bedrock-client';
import { Arr } from '@ephox/katamari';
import { isValidKey } from 'src/main/ts/Utils';

UnitTest.test('UtilsTest', () => {

describe('UtilsTest', () => {
const checkValidKey = (key: string, expected: boolean) => {
const actual = isValidKey(key);
Assertions.assertEq('Key is valid', expected, actual);
Assertions.assertEq('Key should be valid in both camelCase and lowercase', expected, actual);
};

checkValidKey('onKeyUp', true);
checkValidKey('onkeyup', true);
checkValidKey('onDisable', false);
// eslint-disable-next-line max-len
// v-on event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent. ref: https://eslint.vuejs.org/rules/custom-event-name-casing

describe('Valid event name tests', () => {
const validKeys = [
{ key: 'onKeyUp', description: 'camelCase event name "onKeyUp"' },
{ key: 'onkeyup', description: 'lowercase event name "onkeyup"' }
];

Arr.each(validKeys, ({ key, description }) => {
it(`should validate ${description}`, () => {
checkValidKey(key, true);
});
});
});

it('should invalidate unknown event name "onDisable"', () => {
checkValidKey('onDisable', false);
});
});
149 changes: 75 additions & 74 deletions src/test/ts/browser/InitTest.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,84 @@
import { GeneralSteps, Logger, Pipeline, Assertions, Chain, Keyboard, Keys } from '@ephox/agar';
import { UnitTest } from '@ephox/bedrock-client';
import { Assertions, Keyboard, Keys } from '@ephox/agar';
import { pRender, remove } from '../alien/Loader';
import { VersionLoader } from '@tinymce/miniature';
import { cRender, cRemove } from '../alien/Loader';
import { SugarElement } from '@ephox/sugar';
import { describe, it, afterEach, before, context, after } from '@ephox/bedrock-client';
import { cleanupGlobalTinymce, VALID_API_KEY } from '../alien/TestHelper';
import { Arr } from '@ephox/katamari';

UnitTest.asynctest('InitTest', (success, failure) => {
const cFakeType = (str: string) => Chain.op((context: any) => {
context.editor.getBody().innerHTML = '<p>' + str + '</p>';
Keyboard.keystroke(Keys.space(), {}, SugarElement.fromDom(context.editor.getBody()) as SugarElement<Node>);
});
describe('Editor Component Initialization Tests', () => {
// eslint-disable-next-line @typescript-eslint/require-await
const pFakeType = async (str: string, vmContext: any) => {
vmContext.editor.getBody().innerHTML = '<p>' + str + '</p>';
Keyboard.keystroke(Keys.space(), {}, SugarElement.fromDom(vmContext.editor.getBody()) as SugarElement<Node>);
};

Arr.each([ '4', '5', '6', '7' as const ], (version) => {
context(`Version: ${version}`, () => {

before(async () => {
await VersionLoader.pLoadVersion(version);
});

const sTestVersion = (version: '4' | '5' | '6' | '7') => VersionLoader.sWithVersion(
version,
GeneralSteps.sequence([
Logger.t('Should be able to setup editor', Chain.asStep({}, [
cRender(),
Chain.op((context) => {
Assertions.assertEq('Editor should not be inline', false, context.editor.inline);
}),
cRemove
])),
after(() => {
cleanupGlobalTinymce();
});

Logger.t('Should be able to setup editor', Chain.asStep({}, [
cRender({}, `<editor :init="init" :inline=true ></editor>`),
Chain.op((context) => {
Assertions.assertEq('Editor should be inline', true, context.editor.inline);
}),
cRemove
])),
afterEach(() => {
remove();
});

Logger.t('Should be able to setup editor', Chain.asStep({}, [
cRender({ init: { inline: true }}),
Chain.op((context) => {
Assertions.assertEq('Editor should be inline', true, context.editor.inline);
}),
cRemove
])),
it('should not be inline by default', async () => {
const vmContext = await pRender({}, `
<editor
:init="init"
></editor>`);
Assertions.assertEq('Editor should not be inline', false, vmContext.editor.inline);
});

Logger.t('Test one way binding tinymce-vue -> variable', GeneralSteps.sequence([
Logger.t('Test outputFormat="text"', Chain.asStep({}, [
cRender({
content: undefined
}, `
<editor
:init="init"
@update:modelValue="content = $event"
output-format="text"
></editor>
`),
cFakeType('A'),
Chain.op((context) => {
Assertions.assertEq('Content emitted should be of format="text"', 'A', context.vm.content);
}),
cRemove
])),
Logger.t('Test outputFormat="html"', Chain.asStep({}, [
cRender({
content: undefined
}, `
<editor
:init="init"
v-model="content"
output-format="html"
></editor>
`),
cFakeType('A'),
Chain.op((context) => {
Assertions.assertEq('Content emitted should be of format="html"', '<p>A</p>', context.vm.content);
}),
cRemove
])),
])),
])
);
it('should be inline with inline attribute in template', async () => {
const vmContext = await pRender({}, `
<editor
:init="init"
:inline="true"
></editor>`);
Assertions.assertEq('Editor should be inline', true, vmContext.editor.inline);
});

Pipeline.async({}, [
sTestVersion('4'),
sTestVersion('5'),
sTestVersion('6'),
sTestVersion('7'),
], success, failure);
it('should be inline with inline option in init', async () => {
const vmContext = await pRender({ init: { inline: true }});
Assertions.assertEq('Editor should be inline', true, vmContext.editor.inline);
});

it('should handle one-way binding with output-format="text"', async () => {
const vmContext = await pRender({
content: undefined,
}, `
<editor
:init="init"
api-key="${VALID_API_KEY}"
@update:modelValue="content=$event"
output-format="text"
></editor>
`);
await pFakeType('A', vmContext);
Assertions.assertEq('Content emitted should be of format="text"', 'A', vmContext.vm.content);
});

it('should handle one-way binding with output-format="html"', async () => {
const vmContext = await pRender({
content: undefined,
}, `
<editor
:init="init"
api-key="${VALID_API_KEY}"
@update:modelValue="content=$event"
output-format="html"
></editor>
`);
await pFakeType('A', vmContext);
Assertions.assertEq('Content emitted should be of format="html"', '<p>A</p>', vmContext.vm.content);
});
});
});
});
Loading

0 comments on commit ccc0099

Please sign in to comment.