-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(VSCode): Provide timeout and fallback for
KaotoEditorFactory
Currently, VSCode Kaoto build with the main branch is broken due to a missing API implementation that causes for a `promise` to hold forever, preventing the extension to load. The fix is to provide a `timeout` mechanism to unblock the promise, plus a `fallback` mechanism to get sensitive settings default to continue the extension bootstrap process.
- Loading branch information
Showing
5 changed files
with
226 additions
and
3 deletions.
There are no files selected for viewing
122 changes: 122 additions & 0 deletions
122
packages/ui/src/multiplying-architecture/KaotoEditorFactory.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { EditorInitArgs, KogitoEditorEnvelopeContextType } from '@kie-tools-core/editor/dist/api'; | ||
import { ISettingsModel, NodeLabelType } from '../models'; | ||
import { KaotoEditorApp } from './KaotoEditorApp'; | ||
import { KaotoEditorChannelApi } from './KaotoEditorChannelApi'; | ||
import { KaotoEditorFactory } from './KaotoEditorFactory'; | ||
jest.mock('./KaotoEditorApp'); | ||
|
||
describe('KaotoEditorFactory', () => { | ||
afterAll(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should create editor', async () => { | ||
const settingsModel: ISettingsModel = { | ||
catalogUrl: 'catalog-url', | ||
nodeLabel: NodeLabelType.Id, | ||
}; | ||
|
||
const envelopeContext = { | ||
channelApi: { | ||
requests: { | ||
getVSCodeKaotoSettings: () => Promise.resolve(settingsModel), | ||
getCatalogURL: function (): Promise<string | undefined> { | ||
throw new Error('Function not implemented.'); | ||
}, | ||
}, | ||
}, | ||
} as KogitoEditorEnvelopeContextType<KaotoEditorChannelApi>; | ||
const initArgs = {} as EditorInitArgs; | ||
const factory = new KaotoEditorFactory(); | ||
|
||
const editor = await factory.createEditor(envelopeContext, initArgs); | ||
|
||
expect(editor).toBeInstanceOf(KaotoEditorApp); | ||
}); | ||
|
||
it('should get settings', async () => { | ||
const settingsModel: ISettingsModel = { | ||
catalogUrl: 'catalog-url', | ||
nodeLabel: NodeLabelType.Id, | ||
}; | ||
|
||
const getVSCodeKaotoSettingsSpy = jest.fn().mockResolvedValue(settingsModel); | ||
const getCatalogURLSpy = jest.fn().mockRejectedValue(settingsModel); | ||
|
||
const envelopeContext = { | ||
channelApi: { | ||
requests: { | ||
getVSCodeKaotoSettings: getVSCodeKaotoSettingsSpy, | ||
getCatalogURL: getCatalogURLSpy, | ||
}, | ||
}, | ||
} as unknown as KogitoEditorEnvelopeContextType<KaotoEditorChannelApi>; | ||
const initArgs = {} as EditorInitArgs; | ||
const factory = new KaotoEditorFactory(); | ||
|
||
const editor = await factory.createEditor(envelopeContext, initArgs); | ||
|
||
expect(getVSCodeKaotoSettingsSpy).toHaveBeenCalledTimes(1); | ||
expect(getCatalogURLSpy).not.toHaveBeenCalled(); | ||
expect(editor).toBeDefined(); | ||
}); | ||
|
||
it('should fallback to previous API if getVSCodeKaotoSettings is not implemented', async () => { | ||
const getVSCodeKaotoSettingsSpy = jest.fn().mockImplementation(() => new Promise(() => {})); | ||
const getCatalogURLSpy = jest.fn().mockResolvedValue(''); | ||
|
||
const envelopeContext = { | ||
channelApi: { | ||
requests: { | ||
getVSCodeKaotoSettings: getVSCodeKaotoSettingsSpy, | ||
getCatalogURL: getCatalogURLSpy, | ||
}, | ||
}, | ||
} as unknown as KogitoEditorEnvelopeContextType<KaotoEditorChannelApi>; | ||
const initArgs = {} as EditorInitArgs; | ||
const factory = new KaotoEditorFactory(); | ||
|
||
const editor = await factory.createEditor(envelopeContext, initArgs); | ||
|
||
expect(getVSCodeKaotoSettingsSpy).toHaveBeenCalledTimes(1); | ||
expect(getCatalogURLSpy).toHaveBeenCalledTimes(1); | ||
expect(editor).toBeDefined(); | ||
}); | ||
|
||
it('should update catalog URL', async () => { | ||
const settingsModel: ISettingsModel = { | ||
catalogUrl: '', | ||
nodeLabel: NodeLabelType.Id, | ||
}; | ||
|
||
const getVSCodeKaotoSettingsSpy = jest.fn().mockResolvedValue(settingsModel); | ||
const getCatalogURLSpy = jest.fn().mockRejectedValue(settingsModel); | ||
|
||
const envelopeContext = { | ||
channelApi: { | ||
requests: { | ||
getVSCodeKaotoSettings: getVSCodeKaotoSettingsSpy, | ||
getCatalogURL: getCatalogURLSpy, | ||
}, | ||
}, | ||
} as unknown as KogitoEditorEnvelopeContextType<KaotoEditorChannelApi>; | ||
const initArgs = { | ||
resourcesPathPrefix: 'path-prefix', | ||
} as EditorInitArgs; | ||
const factory = new KaotoEditorFactory(); | ||
|
||
const editor = await factory.createEditor(envelopeContext, initArgs); | ||
|
||
expect(KaotoEditorApp).toHaveBeenCalledWith( | ||
envelopeContext, | ||
initArgs, | ||
expect.objectContaining({ | ||
settings: { | ||
catalogUrl: 'path-prefix/camel-catalog/index.json', | ||
nodeLabel: NodeLabelType.Id, | ||
}, | ||
}), | ||
); | ||
expect(editor).toBeDefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { promiseTimeout } from './promise-timeout'; | ||
|
||
describe('promiseTimeout', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should resolve the promise if it resolves before the timeout', async () => { | ||
const promise = Promise.resolve('foo'); | ||
const result = await promiseTimeout(promise, 1_000); | ||
|
||
expect(result).toBe('foo'); | ||
}); | ||
|
||
it('should reject the promise if it rejects before the timeout', async () => { | ||
const promise = Promise.reject(new Error('bar')); | ||
|
||
await expect(promiseTimeout(promise, 1_000)).rejects.toThrow('bar'); | ||
}); | ||
|
||
it('should resolve the promise with the defaultValue when provided, if it takes longer than the timeout', async () => { | ||
const promise = new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve('baz'); | ||
}, 1_000); | ||
}); | ||
|
||
const promiseTimeoutResult = promiseTimeout(promise, 500, 'Lighting fast'); | ||
|
||
jest.advanceTimersByTime(500); | ||
|
||
const result = await promiseTimeoutResult; | ||
|
||
expect(result).toBe('Lighting fast'); | ||
}); | ||
|
||
it('should reject the promise if it takes longer than the timeout', async () => { | ||
const promise = new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve('baz'); | ||
}, 1_000); | ||
}); | ||
|
||
const promiseTimeoutResult = promiseTimeout(promise, 500); | ||
|
||
jest.advanceTimersByTime(500); | ||
|
||
await expect(promiseTimeoutResult).rejects.toThrow('Promise timed out'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export const promiseTimeout: <T>(promise: Promise<T>, timeout: number, defaultValue?: T) => Promise<T> = <T>( | ||
promise: Promise<T>, | ||
timeout: number, | ||
defaultValue?: T, | ||
) => { | ||
let timer: number; | ||
|
||
const timeoutPromise = new Promise<T>((resolve, reject) => { | ||
timer = setTimeout(() => { | ||
if (defaultValue !== undefined) resolve(defaultValue); | ||
|
||
reject(new Error('Promise timed out')); | ||
}, timeout) as unknown as number; | ||
}); | ||
|
||
return Promise.race([promise, timeoutPromise]).finally(() => { | ||
clearTimeout(timer); | ||
}); | ||
}; |