From 6deac3d73d1db2a5d7903bdb28153af387b399e3 Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Wed, 4 Nov 2026 08:58:24 +0530 Subject: [PATCH 1/2] test: fix the 5 failing specs and measure coverage of lib MKT-22142. - Extension and ContentstackUIExtension specs posted real 'init'/'ready' messages with no host to answer; stub Extension.initialize and extension.postRobot instead, and reset the extensionEvent listener between specs. - getField invalid-uid spec asserts the documented message prefix. - 'getField Invalid Uid Multiple' used a valid path and asserted nothing; it now checks the returned instance. - Instrument lib/ with babel-plugin-istanbul; npm run test:coverage reports it (97% lines). npm test no longer writes coverage/. Co-Authored-By: Claude Opus 5.5 --- karma.conf.js | 18 +++++-------- package.json | 1 + test/entrySpec.js | 19 ++++++------- test/extensionSpec.js | 63 ++++++++++++++++++++----------------------- test/helpers/index.js | 8 +++++- test/sdkSpec.js | 44 ++++++++++++------------------ 6 files changed, 68 insertions(+), 85 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index f061e36b..c3d0cd06 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -26,7 +26,7 @@ module.exports = function (config) { logLevel: config.LOG_WARN, // config.LOG_DISABLE, config.LOG_ERROR, config.LOG_INFO, config.LOG_DEBUG frameworks: ['jasmine'], browsers: ['Chrome', 'Firefox', 'Safari'], - reporters: ['kjhtml', 'coverage', 'summary'], + reporters: ['kjhtml', 'summary'], retryLimit: 0, client: { captureConsole: false, @@ -46,16 +46,9 @@ module.exports = function (config) { stats: 'errors-only', logLevel: 'error' }, - coverageIstanbulReporter: { - combineBrowserReports: true, - fixWebpackSourcePaths: true, - reports: ['html', 'text-summary'], + coverageReporter: { dir: path.join(__dirname, 'coverage'), - 'report-config': { - html: { - subdir: 'html' - } - } + reporters: [{ type: 'html', subdir: 'html' }, { type: 'text-summary' }] }, summaryReporter: { show: 'failed', @@ -70,10 +63,11 @@ module.exports = function (config) { test: /\.js$/, loader: 'babel-loader', options: { - presets: ['@babel/preset-env'] + presets: ['@babel/preset-env'], + // instruments lib/ for the coverage reporter (npm run test:coverage) + plugins: [['istanbul', { include: ['lib/**'] }]] } }, - // istanbul-instrumenter-loader removed; use babel-plugin-istanbul for coverage { test: /\.css$/, use: { diff --git a/package.json b/package.json index 97f50c9e..1c0abfdc 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ ], "scripts": { "test": "karma start --single-run --browsers ChromeHeadless karma.conf.js", + "test:coverage": "karma start --single-run --browsers ChromeHeadless --reporters summary,coverage karma.conf.js", "test-local": "karma start --single-run", "generate:docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose", "build": "npx webpack", diff --git a/test/entrySpec.js b/test/entrySpec.js index 744d110c..9350c022 100755 --- a/test/entrySpec.js +++ b/test/entrySpec.js @@ -124,22 +124,19 @@ describe('Entry', function () { } }); - it('getField Invalid Uid Multiple', function () { + // 'group.group.group.0' is a valid path (the inner group is multiple), so it + // returns that instance; the old spec named it invalid and asserted nothing. + it('getField multiple group instance', function () { let uid = 'group.group.group.0'; - try { - entry.getField(uid); - } catch (e) { - expect(e.message).toEqual('Invalid uid, Field not found'); - } + let field = entry.getField(uid); + expect(field.uid).toEqual(uid); + expect(field.getData()).toEqual(testData.entry.group.group.group[0]); }); it('getField Invalid Uid', function () { - try { - entry.getField('invaliduid'); - } catch (e) { - expect(e.message).toEqual('Invalid uid, Field not found'); - } + // Documented message prefix; b6d18c5 appends the cause after it. + expect(() => entry.getField('invaliduid')).toThrowError(/^Invalid uid, Field not found/); }); it('onSave Callback must be a function', function () { diff --git a/test/extensionSpec.js b/test/extensionSpec.js index 85d9dfe5..b5a46158 100755 --- a/test/extensionSpec.js +++ b/test/extensionSpec.js @@ -1,51 +1,46 @@ import originalTestData from './data/testData.json'; import Extension from '../lib/extension.js'; -import ExtensionModule from '../lib/extension.js'; +import helpers from './helpers'; -let testData = JSON.parse(JSON.stringify(originalTestData)) +// Extension.initialize posts 'init' to the host and karma has no host to answer, +// so these specs build Extension from init data, as the host would return it. describe('Extension', () => { - let newExtension; - let sendToParent = function (channel) { return Promise.resolve({ data: testData }); }; - let on = function (event, cbf) { - setTimeout(() => { - cbf({ data: { data: testData.entry, name: 'entrySave' } }); - cbf({ data: { data: {}, name: 'entryPublish' } }); - cbf({ data: { data: {}, name: 'entryUnPublish' } }); - cbf({ data: { data: testData.entry, name: 'entryChange' } }); - cbf({ data: { state: 'full_width', name: 'dashboardResize' } }); - cbf({ data: { data: 'sample', name: 'extensionFieldChange' } }); - }, 300); - }; + let testData; beforeEach(function () { - newExtension = ExtensionModule; + testData = helpers.clone(originalTestData); + helpers.resetExtensionEventListener(); }); - it('initialize', function (done) { - newExtension.initialize().then((data) => { - let extension = new newExtension(data); - expect(extension.config).toEqual(testData.config); - expect(extension.currentUser).toEqual(testData.user); - expect(extension.type).toEqual('FIELD'); + it('initialize', function () { + let extension = new Extension({ data: testData }); + expect(extension.config).toEqual(testData.config); + expect(extension.currentUser).toEqual(testData.user); + expect(extension.type).toEqual('FIELD'); + expect(extension.field).toBeDefined(); + expect(extension.entry).toBeDefined(); + expect(extension.window).toBeDefined(); - testData.type = 'SIDEBAR'; - extension = new newExtension(data); - expect(extension.type).toEqual(testData.type); - expect(extension.window).toBeUndefined(); - expect(extension.field).toBeUndefined(); + helpers.resetExtensionEventListener(); + testData.type = 'SIDEBAR'; + extension = new Extension({ data: testData }); + expect(extension.type).toEqual(testData.type); + expect(extension.window).toBeUndefined(); + expect(extension.field).toBeUndefined(); - testData.type = 'DASHBOARD'; - extension = new newExtension(data); - expect(extension.type).toEqual(testData.type); - expect(extension.entry).toBeUndefined(); - expect(extension.field).toBeUndefined(); - done(); - }); + helpers.resetExtensionEventListener(); + testData.type = 'DASHBOARD'; + extension = new Extension({ data: testData }); + expect(extension.type).toEqual(testData.type); + expect(extension.entry).toBeUndefined(); + expect(extension.field).toBeUndefined(); }); it('setReady', function (done) { - let extension = new newExtension({ data: testData }); + let extension = new Extension({ data: testData }); + extension.postRobot = { sendToParent: jasmine.createSpy('sendToParent').and.returnValue(Promise.resolve()) }; extension.setReady().then(() => { + expect(extension.postRobot.sendToParent).toHaveBeenCalledWith('ready'); done(); }); }); diff --git a/test/helpers/index.js b/test/helpers/index.js index 89cc5339..ef85e06a 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -1,2 +1,8 @@ const clone = (source) => JSON.parse(JSON.stringify(source)); -module.exports.clone = clone ; \ No newline at end of file +module.exports.clone = clone ; +// Each Extension registers one post-robot 'extensionEvent' listener and post-robot +// refuses a second one for the same window, so specs that build several reset it. +const resetExtensionEventListener = () => { + delete window.__postRobot__.requestListeners.extensionEvent; +}; +module.exports.resetExtensionEventListener = resetExtensionEventListener; diff --git a/test/sdkSpec.js b/test/sdkSpec.js index 25656885..3065e5dc 100755 --- a/test/sdkSpec.js +++ b/test/sdkSpec.js @@ -1,53 +1,43 @@ import testData from './data/testData.json'; import ContentstackUIExtension from '../lib/index.js'; -import MainModule from '../lib/index.js'; -import ExtensionModule from '../lib/extension.js'; +import Extension from '../lib/extension.js'; import pkg from '../package.json'; +import helpers from './helpers'; - +// karma has no host to answer 'init', so Extension.initialize is stubbed with +// what the host returns. describe('ContentstackUIExtension', () => { - let newContentstackUIExtension; - let sendToParent; - let on = function (event, cbf) { - setTimeout(() => { - cbf({ data: testData.entry }); - }, 300); - }; - + beforeEach(function () { + ContentstackUIExtension._extension = undefined; + helpers.resetExtensionEventListener(); + }); it('init', function (done) { - sendToParent = function (channel) { return Promise.resolve({ data: testData }); }; - - // Directly use the imported modules (mocking can be done with jest or sinon if needed) - let newExtensionModule = ExtensionModule; - newContentstackUIExtension = MainModule; + spyOn(Extension, 'initialize').and.returnValue(Promise.resolve({ data: helpers.clone(testData) })); - - newContentstackUIExtension.init().then((extension) => { + ContentstackUIExtension.init().then((extension) => { + expect(Extension.initialize).toHaveBeenCalledWith(pkg.version); expect(extension.config).toEqual(testData.config); expect(extension.currentUser).toEqual(testData.user); expect(extension.type).toEqual('FIELD'); - newContentstackUIExtension.init().then((newExtesnion) => { // must return same extension object once initialized - expect(extension).toEqual(newExtesnion); + ContentstackUIExtension.init().then((newExtension) => { // must return same extension object once initialized + expect(newExtension).toBe(extension); + expect(Extension.initialize.calls.count()).toEqual(1); done(); }); }); }); it('init failure', function (done) { - sendToParent = function (channel) { return Promise.reject('Sample Error'); }; - - let newExtensionModule = ExtensionModule; - newContentstackUIExtension = MainModule; - + spyOn(Extension, 'initialize').and.returnValue(Promise.reject('Sample Error')); - newContentstackUIExtension.init().catch((error) => { + ContentstackUIExtension.init().catch((error) => { expect(error).toEqual('Sample Error'); done(); }); }); it('SDK_VERSION', function () { - expect(newContentstackUIExtension.SDK_VERSION).toEqual(pkg.version); + expect(ContentstackUIExtension.SDK_VERSION).toEqual(pkg.version); }); }); From ec22d23b8f96eeb381a11ef55389df332a5dc5d5 Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Wed, 4 Nov 2026 09:12:01 +0530 Subject: [PATCH 2/2] test: the getField error prefix is the code's contract, not documented MKT-22142. Co-Authored-By: Claude Opus 5.5 --- test/entrySpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/entrySpec.js b/test/entrySpec.js index 9350c022..2784b8f7 100755 --- a/test/entrySpec.js +++ b/test/entrySpec.js @@ -135,7 +135,7 @@ describe('Entry', function () { it('getField Invalid Uid', function () { - // Documented message prefix; b6d18c5 appends the cause after it. + // Current (undocumented) message prefix, as 2.2.3 throws it; b6d18c5 appends the cause. expect(() => entry.getField('invaliduid')).toThrowError(/^Invalid uid, Field not found/); });