diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-24 00:10:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-24 00:10:02 +0300 |
commit | abd24a801e8dbe0942558dd2b2cad90e7e01938e (patch) | |
tree | 27b00caff863be07505fa60e63ece1d55f10e2b9 /spec/frontend | |
parent | fee19ef336bc64155e0d9e8697834ff529bb6d93 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r-- | spec/frontend/editor/helpers.js | 33 | ||||
-rw-r--r-- | spec/frontend/editor/source_editor_extension_spec.js | 24 | ||||
-rw-r--r-- | spec/frontend/editor/source_editor_instance_spec.js | 158 | ||||
-rw-r--r-- | spec/frontend/editor/source_editor_markdown_ext_spec.js | 2 | ||||
-rw-r--r-- | spec/frontend/editor/source_editor_spec.js | 107 |
5 files changed, 160 insertions, 164 deletions
diff --git a/spec/frontend/editor/helpers.js b/spec/frontend/editor/helpers.js index 6f7cdf6efb3..c77be4f8c58 100644 --- a/spec/frontend/editor/helpers.js +++ b/spec/frontend/editor/helpers.js @@ -1,4 +1,4 @@ -export class MyClassExtension { +export class SEClassExtension { // eslint-disable-next-line class-methods-use-this provides() { return { @@ -8,7 +8,7 @@ export class MyClassExtension { } } -export function MyFnExtension() { +export function SEFnExtension() { return { fnExtMethod: () => 'fn own method', provides: () => { @@ -19,7 +19,7 @@ export function MyFnExtension() { }; } -export const MyConstExt = () => { +export const SEConstExt = () => { return { provides: () => { return { @@ -29,6 +29,33 @@ export const MyConstExt = () => { }; }; +export function SEWithSetupExt() { + return { + onSetup: (setupOptions = {}, instance) => { + if (setupOptions && !Array.isArray(setupOptions)) { + Object.entries(setupOptions).forEach(([key, value]) => { + Object.assign(instance, { + [key]: value, + }); + }); + } + }, + provides: () => { + return { + returnInstanceAndProps: (instance, stringProp, objProp = {}) => { + return [stringProp, objProp, instance]; + }, + returnInstance: (instance) => { + return instance; + }, + giveMeContext: () => { + return this; + }, + }; + }, + }; +} + export const conflictingExtensions = { WithInstanceExt: () => { return { diff --git a/spec/frontend/editor/source_editor_extension_spec.js b/spec/frontend/editor/source_editor_extension_spec.js index 6f2eb07a043..de3f9da0aed 100644 --- a/spec/frontend/editor/source_editor_extension_spec.js +++ b/spec/frontend/editor/source_editor_extension_spec.js @@ -22,15 +22,15 @@ describe('Editor Extension', () => { it.each` definition | setupOptions | expectedName - ${helpers.MyClassExtension} | ${undefined} | ${'MyClassExtension'} - ${helpers.MyClassExtension} | ${{}} | ${'MyClassExtension'} - ${helpers.MyClassExtension} | ${dummyObj} | ${'MyClassExtension'} - ${helpers.MyFnExtension} | ${undefined} | ${'MyFnExtension'} - ${helpers.MyFnExtension} | ${{}} | ${'MyFnExtension'} - ${helpers.MyFnExtension} | ${dummyObj} | ${'MyFnExtension'} - ${helpers.MyConstExt} | ${undefined} | ${'MyConstExt'} - ${helpers.MyConstExt} | ${{}} | ${'MyConstExt'} - ${helpers.MyConstExt} | ${dummyObj} | ${'MyConstExt'} + ${helpers.SEClassExtension} | ${undefined} | ${'SEClassExtension'} + ${helpers.SEClassExtension} | ${{}} | ${'SEClassExtension'} + ${helpers.SEClassExtension} | ${dummyObj} | ${'SEClassExtension'} + ${helpers.SEFnExtension} | ${undefined} | ${'SEFnExtension'} + ${helpers.SEFnExtension} | ${{}} | ${'SEFnExtension'} + ${helpers.SEFnExtension} | ${dummyObj} | ${'SEFnExtension'} + ${helpers.SEConstExt} | ${undefined} | ${'SEConstExt'} + ${helpers.SEConstExt} | ${{}} | ${'SEConstExt'} + ${helpers.SEConstExt} | ${dummyObj} | ${'SEConstExt'} `( 'correctly creates extension for definition = $definition and setupOptions = $setupOptions', ({ definition, setupOptions, expectedName }) => { @@ -51,9 +51,9 @@ describe('Editor Extension', () => { describe('api', () => { it.each` definition | expectedKeys - ${helpers.MyClassExtension} | ${['shared', 'classExtMethod']} - ${helpers.MyFnExtension} | ${['fnExtMethod']} - ${helpers.MyConstExt} | ${['constExtMethod']} + ${helpers.SEClassExtension} | ${['shared', 'classExtMethod']} + ${helpers.SEFnExtension} | ${['fnExtMethod']} + ${helpers.SEConstExt} | ${['constExtMethod']} `('correctly returns API for $definition', ({ definition, expectedKeys }) => { const extension = new EditorExtension({ definition }); const expectedApi = Object.fromEntries( diff --git a/spec/frontend/editor/source_editor_instance_spec.js b/spec/frontend/editor/source_editor_instance_spec.js index 87b20a4ba73..a46eea4c4cd 100644 --- a/spec/frontend/editor/source_editor_instance_spec.js +++ b/spec/frontend/editor/source_editor_instance_spec.js @@ -6,23 +6,29 @@ import { EDITOR_EXTENSION_NOT_REGISTERED_ERROR, EDITOR_EXTENSION_NOT_SPECIFIED_FOR_UNUSE_ERROR, } from '~/editor/constants'; -import Instance from '~/editor/source_editor_instance'; +import SourceEditorInstance from '~/editor/source_editor_instance'; import { sprintf } from '~/locale'; -import { MyClassExtension, conflictingExtensions, MyFnExtension, MyConstExt } from './helpers'; +import { + SEClassExtension, + conflictingExtensions, + SEFnExtension, + SEConstExt, + SEWithSetupExt, +} from './helpers'; describe('Source Editor Instance', () => { let seInstance; const defSetupOptions = { foo: 'bar' }; const fullExtensionsArray = [ - { definition: MyClassExtension }, - { definition: MyFnExtension }, - { definition: MyConstExt }, + { definition: SEClassExtension }, + { definition: SEFnExtension }, + { definition: SEConstExt }, ]; const fullExtensionsArrayWithOptions = [ - { definition: MyClassExtension, setupOptions: defSetupOptions }, - { definition: MyFnExtension, setupOptions: defSetupOptions }, - { definition: MyConstExt, setupOptions: defSetupOptions }, + { definition: SEClassExtension, setupOptions: defSetupOptions }, + { definition: SEFnExtension, setupOptions: defSetupOptions }, + { definition: SEConstExt, setupOptions: defSetupOptions }, ]; const fooFn = jest.fn(); @@ -40,26 +46,26 @@ describe('Source Editor Instance', () => { }); it('sets up the registry for the methods coming from extensions', () => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); expect(seInstance.methods).toBeDefined(); - seInstance.use({ definition: MyClassExtension }); + seInstance.use({ definition: SEClassExtension }); expect(seInstance.methods).toEqual({ - shared: 'MyClassExtension', - classExtMethod: 'MyClassExtension', + shared: 'SEClassExtension', + classExtMethod: 'SEClassExtension', }); - seInstance.use({ definition: MyFnExtension }); + seInstance.use({ definition: SEFnExtension }); expect(seInstance.methods).toEqual({ - shared: 'MyClassExtension', - classExtMethod: 'MyClassExtension', - fnExtMethod: 'MyFnExtension', + shared: 'SEClassExtension', + classExtMethod: 'SEClassExtension', + fnExtMethod: 'SEFnExtension', }); }); describe('proxy', () => { it('returns prop from an extension if extension provides it', () => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); seInstance.use({ definition: DummyExt }); expect(fooFn).not.toHaveBeenCalled(); @@ -67,8 +73,58 @@ describe('Source Editor Instance', () => { expect(fooFn).toHaveBeenCalled(); }); + it.each` + stringPropToPass | objPropToPass | setupOptions + ${undefined} | ${undefined} | ${undefined} + ${'prop'} | ${undefined} | ${undefined} + ${'prop'} | ${[]} | ${undefined} + ${'prop'} | ${{}} | ${undefined} + ${'prop'} | ${{ alpha: 'beta' }} | ${undefined} + ${'prop'} | ${{ alpha: 'beta' }} | ${defSetupOptions} + ${'prop'} | ${undefined} | ${defSetupOptions} + ${undefined} | ${undefined} | ${defSetupOptions} + ${''} | ${{}} | ${defSetupOptions} + `( + 'correctly passes arguments ("$stringPropToPass", "$objPropToPass") and instance (with "$setupOptions" setupOptions) to extension methods', + ({ stringPropToPass, objPropToPass, setupOptions }) => { + seInstance = new SourceEditorInstance(); + seInstance.use({ definition: SEWithSetupExt, setupOptions }); + + const [stringProp, objProp, instance] = seInstance.returnInstanceAndProps( + stringPropToPass, + objPropToPass, + ); + const expectedObjProps = objPropToPass || {}; + + expect(instance).toBe(seInstance); + expect(stringProp).toBe(stringPropToPass); + expect(objProp).toEqual(expectedObjProps); + if (setupOptions) { + Object.keys(setupOptions).forEach((key) => { + expect(instance[key]).toBe(setupOptions[key]); + }); + } + }, + ); + + it('correctly passes instance to the methods even if no additional props have been passed', () => { + seInstance = new SourceEditorInstance(); + seInstance.use({ definition: SEWithSetupExt }); + + const instance = seInstance.returnInstance(); + + expect(instance).toBe(seInstance); + }); + + it("correctly sets the context of the 'this' keyword for the extension's methods", () => { + seInstance = new SourceEditorInstance(); + seInstance.use({ definition: SEWithSetupExt }); + + expect(seInstance.giveMeContext().constructor).toEqual(SEWithSetupExt); + }); + it('returns props from SE instance itself if no extension provides the prop', () => { - seInstance = new Instance({ + seInstance = new SourceEditorInstance({ use: fooFn, }); jest.spyOn(seInstance, 'use').mockImplementation(() => {}); @@ -80,7 +136,7 @@ describe('Source Editor Instance', () => { }); it('returns props from Monaco instance when the prop does not exist on the SE instance', () => { - seInstance = new Instance({ + seInstance = new SourceEditorInstance({ fooFn, }); @@ -92,13 +148,13 @@ describe('Source Editor Instance', () => { describe('public API', () => { it.each(['use', 'unuse'], 'provides "%s" as public method by default', (method) => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); expect(seInstance[method]).toBeDefined(); }); describe('use', () => { it('extends the SE instance with methods provided by an extension', () => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); seInstance.use({ definition: DummyExt }); expect(fooFn).not.toHaveBeenCalled(); @@ -108,15 +164,15 @@ describe('Source Editor Instance', () => { it.each` extensions | expectedProps - ${{ definition: MyClassExtension }} | ${['shared', 'classExtMethod']} - ${{ definition: MyFnExtension }} | ${['fnExtMethod']} - ${{ definition: MyConstExt }} | ${['constExtMethod']} + ${{ definition: SEClassExtension }} | ${['shared', 'classExtMethod']} + ${{ definition: SEFnExtension }} | ${['fnExtMethod']} + ${{ definition: SEConstExt }} | ${['constExtMethod']} ${fullExtensionsArray} | ${['shared', 'classExtMethod', 'fnExtMethod', 'constExtMethod']} ${fullExtensionsArrayWithOptions} | ${['shared', 'classExtMethod', 'fnExtMethod', 'constExtMethod']} `( 'Should register $expectedProps when extension is "$extensions"', ({ extensions, expectedProps }) => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); expect(seInstance.extensionsAPI).toHaveLength(0); seInstance.use(extensions); @@ -127,15 +183,15 @@ describe('Source Editor Instance', () => { it.each` definition | preInstalledExtDefinition | expectedErrorProp - ${conflictingExtensions.WithInstanceExt} | ${MyClassExtension} | ${'use'} + ${conflictingExtensions.WithInstanceExt} | ${SEClassExtension} | ${'use'} ${conflictingExtensions.WithInstanceExt} | ${null} | ${'use'} ${conflictingExtensions.WithAnotherExt} | ${null} | ${undefined} - ${conflictingExtensions.WithAnotherExt} | ${MyClassExtension} | ${'shared'} - ${MyClassExtension} | ${conflictingExtensions.WithAnotherExt} | ${'shared'} + ${conflictingExtensions.WithAnotherExt} | ${SEClassExtension} | ${'shared'} + ${SEClassExtension} | ${conflictingExtensions.WithAnotherExt} | ${'shared'} `( 'logs the naming conflict error when registering $definition', ({ definition, preInstalledExtDefinition, expectedErrorProp }) => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); jest.spyOn(console, 'error').mockImplementation(() => {}); if (preInstalledExtDefinition) { @@ -175,7 +231,7 @@ describe('Source Editor Instance', () => { `( 'Should throw $thrownError when extension is "$extensions"', ({ extensions, thrownError }) => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); const useExtension = () => { seInstance.use(extensions); }; @@ -188,24 +244,24 @@ describe('Source Editor Instance', () => { beforeEach(() => { extensionStore = new Map(); - seInstance = new Instance({}, extensionStore); + seInstance = new SourceEditorInstance({}, extensionStore); }); it('stores _instances_ of the used extensions in a global registry', () => { - const extension = seInstance.use({ definition: MyClassExtension }); + const extension = seInstance.use({ definition: SEClassExtension }); expect(extensionStore.size).toBe(1); - expect(extensionStore.entries().next().value).toEqual(['MyClassExtension', extension]); + expect(extensionStore.entries().next().value).toEqual(['SEClassExtension', extension]); }); it('does not duplicate entries in the registry', () => { jest.spyOn(extensionStore, 'set'); - const extension1 = seInstance.use({ definition: MyClassExtension }); - seInstance.use({ definition: MyClassExtension }); + const extension1 = seInstance.use({ definition: SEClassExtension }); + seInstance.use({ definition: SEClassExtension }); expect(extensionStore.set).toHaveBeenCalledTimes(1); - expect(extensionStore.set).toHaveBeenCalledWith('MyClassExtension', extension1); + expect(extensionStore.set).toHaveBeenCalledWith('SEClassExtension', extension1); }); it.each` @@ -222,20 +278,20 @@ describe('Source Editor Instance', () => { jest.spyOn(extensionStore, 'set'); const extension1 = seInstance.use({ - definition: MyClassExtension, + definition: SEClassExtension, setupOptions: currentSetupOptions, }); const extension2 = seInstance.use({ - definition: MyClassExtension, + definition: SEClassExtension, setupOptions: newSetupOptions, }); expect(extensionStore.size).toBe(1); expect(extensionStore.set).toHaveBeenCalledTimes(expectedCallTimes); if (expectedCallTimes > 1) { - expect(extensionStore.set).toHaveBeenCalledWith('MyClassExtension', extension2); + expect(extensionStore.set).toHaveBeenCalledWith('SEClassExtension', extension2); } else { - expect(extensionStore.set).toHaveBeenCalledWith('MyClassExtension', extension1); + expect(extensionStore.set).toHaveBeenCalledWith('SEClassExtension', extension1); } }, ); @@ -252,7 +308,7 @@ describe('Source Editor Instance', () => { `( `Should throw "${EDITOR_EXTENSION_NOT_SPECIFIED_FOR_UNUSE_ERROR}" when extension is "$unuseExtension"`, ({ unuseExtension, thrownError }) => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); const unuse = () => { seInstance.unuse(unuseExtension); }; @@ -262,16 +318,16 @@ describe('Source Editor Instance', () => { it.each` initExtensions | unuseExtensionIndex | remainingAPI - ${{ definition: MyClassExtension }} | ${0} | ${[]} - ${{ definition: MyFnExtension }} | ${0} | ${[]} - ${{ definition: MyConstExt }} | ${0} | ${[]} + ${{ definition: SEClassExtension }} | ${0} | ${[]} + ${{ definition: SEFnExtension }} | ${0} | ${[]} + ${{ definition: SEConstExt }} | ${0} | ${[]} ${fullExtensionsArray} | ${0} | ${['fnExtMethod', 'constExtMethod']} ${fullExtensionsArray} | ${1} | ${['shared', 'classExtMethod', 'constExtMethod']} ${fullExtensionsArray} | ${2} | ${['shared', 'classExtMethod', 'fnExtMethod']} `( 'un-registers properties introduced by single extension $unuseExtension', ({ initExtensions, unuseExtensionIndex, remainingAPI }) => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); const extensions = seInstance.use(initExtensions); if (Array.isArray(initExtensions)) { @@ -291,7 +347,7 @@ describe('Source Editor Instance', () => { `( 'un-registers properties introduced by multiple extensions $unuseExtension', ({ unuseExtensionIndex, remainingAPI }) => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); const extensions = seInstance.use(fullExtensionsArray); const extensionsToUnuse = extensions.filter((ext, index) => unuseExtensionIndex.includes(index), @@ -304,11 +360,11 @@ describe('Source Editor Instance', () => { it('it does not remove entry from the global registry to keep for potential future re-use', () => { const extensionStore = new Map(); - seInstance = new Instance({}, extensionStore); + seInstance = new SourceEditorInstance({}, extensionStore); const extensions = seInstance.use(fullExtensionsArray); const verifyExpectations = () => { const entries = extensionStore.entries(); - const mockExtensions = ['MyClassExtension', 'MyFnExtension', 'MyConstExt']; + const mockExtensions = ['SEClassExtension', 'SEFnExtension', 'SEConstExt']; expect(extensionStore.size).toBe(mockExtensions.length); mockExtensions.forEach((ext, index) => { expect(entries.next().value).toEqual([ext, extensions[index]]); @@ -326,7 +382,7 @@ describe('Source Editor Instance', () => { beforeEach(() => { instanceModel = monacoEditor.createModel(''); - seInstance = new Instance({ + seInstance = new SourceEditorInstance({ getModel: () => instanceModel, }); }); @@ -363,7 +419,7 @@ describe('Source Editor Instance', () => { }; it('passes correct arguments to callback fns when using an extension', () => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); seInstance.use({ definition: MyFullExtWithCallbacks, setupOptions: defSetupOptions, @@ -373,7 +429,7 @@ describe('Source Editor Instance', () => { }); it('passes correct arguments to callback fns when un-using an extension', () => { - seInstance = new Instance(); + seInstance = new SourceEditorInstance(); const extension = seInstance.use({ definition: MyFullExtWithCallbacks, setupOptions: defSetupOptions, diff --git a/spec/frontend/editor/source_editor_markdown_ext_spec.js b/spec/frontend/editor/source_editor_markdown_ext_spec.js index 245c6c28d31..4a53f870f6d 100644 --- a/spec/frontend/editor/source_editor_markdown_ext_spec.js +++ b/spec/frontend/editor/source_editor_markdown_ext_spec.js @@ -57,7 +57,7 @@ describe('Markdown Extension for Source Editor', () => { blobPath: markdownPath, blobContent: text, }); - editor.use(new EditorMarkdownExtension({ instance, previewMarkdownPath })); + instance.use(new EditorMarkdownExtension({ instance, previewMarkdownPath })); panelSpy = jest.spyOn(EditorMarkdownExtension, 'togglePreviewPanel'); }); diff --git a/spec/frontend/editor/source_editor_spec.js b/spec/frontend/editor/source_editor_spec.js index d87d373c952..f1b887b2dc1 100644 --- a/spec/frontend/editor/source_editor_spec.js +++ b/spec/frontend/editor/source_editor_spec.js @@ -1,6 +1,5 @@ /* eslint-disable max-classes-per-file */ import { editor as monacoEditor, languages as monacoLanguages } from 'monaco-editor'; -import waitForPromises from 'helpers/wait_for_promises'; import { SOURCE_EDITOR_INSTANCE_ERROR_NO_EL, URI_PREFIX, @@ -531,105 +530,19 @@ describe('Base editor', () => { instance.use(FunctionExt); expect(instance.inst()).toEqual(editor.instances[0]); }); - }); - - describe('extensions as an instance parameter', () => { - let editorExtensionSpy; - const instanceConstructor = (extensions = []) => { - return editor.createInstance({ - el: editorEl, - blobPath, - blobContent, - extensions, - }); - }; - - beforeEach(() => { - editorExtensionSpy = jest - .spyOn(SourceEditor, 'pushToImportsArray') - .mockImplementation((arr) => { - arr.push( - Promise.resolve({ - default: {}, - }), - ); - }); - }); - - it.each([undefined, [], [''], ''])( - 'does not fail and makes no fetch if extensions is %s', - () => { - instance = instanceConstructor(null); - expect(editorExtensionSpy).not.toHaveBeenCalled(); - }, - ); - - it.each` - type | value | callsCount - ${'simple string'} | ${'foo'} | ${1} - ${'combined string'} | ${'foo, bar'} | ${2} - ${'array of strings'} | ${['foo', 'bar']} | ${2} - `('accepts $type as an extension parameter', ({ value, callsCount }) => { - instance = instanceConstructor(value); - expect(editorExtensionSpy).toHaveBeenCalled(); - expect(editorExtensionSpy.mock.calls).toHaveLength(callsCount); - }); - it.each` - desc | path | expectation - ${'~/editor'} | ${'foo'} | ${'~/editor/foo'} - ${'~/CUSTOM_PATH with leading slash'} | ${'/my_custom_path/bar'} | ${'~/my_custom_path/bar'} - ${'~/CUSTOM_PATH without leading slash'} | ${'my_custom_path/delta'} | ${'~/my_custom_path/delta'} - `('fetches extensions from $desc path', ({ path, expectation }) => { - instance = instanceConstructor(path); - expect(editorExtensionSpy).toHaveBeenCalledWith(expect.any(Array), expectation); - }); - - it('emits EDITOR_READY_EVENT event after all extensions were applied', async () => { - const calls = []; - const eventSpy = jest.fn().mockImplementation(() => { - calls.push('event'); - }); - const useSpy = jest.fn().mockImplementation(() => { - calls.push('use'); - }); - jest.spyOn(SourceEditor, 'convertMonacoToELInstance').mockImplementation((inst) => { - const decoratedInstance = inst; - decoratedInstance.use = useSpy; - return decoratedInstance; + it('emits the EDITOR_READY_EVENT event after setting up the instance', () => { + jest.spyOn(monacoEditor, 'create').mockImplementation(() => { + return { + setModel: jest.fn(), + onDidDispose: jest.fn(), + }; }); + const eventSpy = jest.fn(); editorEl.addEventListener(EDITOR_READY_EVENT, eventSpy); - instance = instanceConstructor('foo, bar'); - await waitForPromises(); - expect(useSpy.mock.calls).toHaveLength(2); - expect(calls).toEqual(['use', 'use', 'event']); - }); - }); - - describe('multiple instances', () => { - let inst1; - let inst2; - let editorEl1; - let editorEl2; - - beforeEach(() => { - setFixtures('<div id="editor1"></div><div id="editor2"></div>'); - editorEl1 = document.getElementById('editor1'); - editorEl2 = document.getElementById('editor2'); - inst1 = editor.createInstance({ el: editorEl1, blobPath: `foo-${blobPath}` }); - inst2 = editor.createInstance({ el: editorEl2, blobPath: `bar-${blobPath}` }); - }); - - afterEach(() => { - editor.dispose(); - editorEl1.remove(); - editorEl2.remove(); - }); - - it('extends all instances if no specific instance is passed', () => { - editor.use(AlphaExt); - expect(inst1.alpha()).toEqual(alphaRes); - expect(inst2.alpha()).toEqual(alphaRes); + expect(eventSpy).not.toHaveBeenCalled(); + instance = editor.createInstance({ el: editorEl }); + expect(eventSpy).toHaveBeenCalled(); }); }); }); |