From c15b3c507cafa6adb8b642af4c170e80596563e7 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Thu, 29 Feb 2024 17:36:51 +0100 Subject: [PATCH] MOBILE-4272 workshop: Decouple from initial bundle --- .../accumulative/accumulative.module.ts | 11 +---- .../accumulative/services/handler.ts | 13 ++++- .../assessment/comments/comments.module.ts | 7 +-- .../assessment/comments/services/handler.ts | 13 ++++- .../assessment/numerrors/numerrors.module.ts | 11 +---- .../assessment/numerrors/services/handler.ts | 13 ++++- .../assessment/rubric/rubric.module.ts | 7 +-- .../assessment/rubric/services/handler.ts | 13 ++++- .../workshop/services/handlers/prefetch.ts | 7 ++- .../workshop/services/handlers/sync-cron.ts | 7 ++- .../workshop/tests/behat/basic_usage.feature | 8 +++ src/addons/mod/workshop/workshop.module.ts | 11 ++--- src/core/utils/async-instance.ts | 22 +++++++++ src/core/utils/tests/async-instance.test.ts | 49 ++++++++++++++++++- src/core/utils/types.ts | 41 ++++++++++++++++ 15 files changed, 191 insertions(+), 42 deletions(-) diff --git a/src/addons/mod/workshop/assessment/accumulative/accumulative.module.ts b/src/addons/mod/workshop/assessment/accumulative/accumulative.module.ts index e093a89b7..5a9cb63e4 100644 --- a/src/addons/mod/workshop/assessment/accumulative/accumulative.module.ts +++ b/src/addons/mod/workshop/assessment/accumulative/accumulative.module.ts @@ -15,9 +15,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate'; import { CoreSharedModule } from '@/core/shared.module'; -import { - AddonModWorkshopAssessmentStrategyAccumulativeHandler, -} from '@addons/mod/workshop/assessment/accumulative/services/handler-lazy'; +import { getAssessmentStrategyHandlerInstance } from './services/handler'; @NgModule({ imports: [ @@ -28,12 +26,7 @@ import { provide: APP_INITIALIZER, multi: true, useValue: () => { - // TODO use async instances - // AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); - - AddonWorkshopAssessmentStrategyDelegate.registerHandler( - AddonModWorkshopAssessmentStrategyAccumulativeHandler.instance, - ); + AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); }, }, ], diff --git a/src/addons/mod/workshop/assessment/accumulative/services/handler.ts b/src/addons/mod/workshop/assessment/accumulative/services/handler.ts index 869652eee..9b3e23c07 100644 --- a/src/addons/mod/workshop/assessment/accumulative/services/handler.ts +++ b/src/addons/mod/workshop/assessment/accumulative/services/handler.ts @@ -18,6 +18,7 @@ import { ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_ACCUMULATIVE_NAME, ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_ACCUMULATIVE_STRATEGY_NAME, } from '@addons/mod/workshop/assessment/constants'; +import type { AddonModWorkshopAssessmentStrategyAccumulativeHandlerLazyService } from './handler-lazy'; export class AddonModWorkshopAssessmentStrategyAccumulativeHandlerService { @@ -32,13 +33,23 @@ export class AddonModWorkshopAssessmentStrategyAccumulativeHandlerService { * @returns Assessment strategy handler. */ export function getAssessmentStrategyHandlerInstance(): AddonWorkshopAssessmentStrategyHandler { - const lazyHandler = asyncInstance(async () => { + const lazyHandler = asyncInstance< + AddonModWorkshopAssessmentStrategyAccumulativeHandlerLazyService, + AddonModWorkshopAssessmentStrategyAccumulativeHandlerService + >(async () => { const { AddonModWorkshopAssessmentStrategyAccumulativeHandler } = await import('./handler-lazy'); return AddonModWorkshopAssessmentStrategyAccumulativeHandler.instance; }); lazyHandler.setEagerInstance(new AddonModWorkshopAssessmentStrategyAccumulativeHandlerService()); + lazyHandler.setLazyInstanceMethods([ + 'isEnabled', + 'getComponent', + 'getOriginalValues', + 'hasDataChanged', + 'prepareAssessmentData', + ]); return lazyHandler; } diff --git a/src/addons/mod/workshop/assessment/comments/comments.module.ts b/src/addons/mod/workshop/assessment/comments/comments.module.ts index c6820effe..33d72d4b5 100644 --- a/src/addons/mod/workshop/assessment/comments/comments.module.ts +++ b/src/addons/mod/workshop/assessment/comments/comments.module.ts @@ -15,7 +15,7 @@ import { CoreSharedModule } from '@/core/shared.module'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate'; -import { AddonModWorkshopAssessmentStrategyCommentsHandler } from '@addons/mod/workshop/assessment/comments/services/handler-lazy'; +import { getAssessmentStrategyHandlerInstance } from './services/handler'; @NgModule({ imports: [ @@ -26,10 +26,7 @@ import { AddonModWorkshopAssessmentStrategyCommentsHandler } from '@addons/mod/w provide: APP_INITIALIZER, multi: true, useValue: () => { - // TODO use async instances - // AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); - - AddonWorkshopAssessmentStrategyDelegate.registerHandler(AddonModWorkshopAssessmentStrategyCommentsHandler.instance); + AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); }, }, ], diff --git a/src/addons/mod/workshop/assessment/comments/services/handler.ts b/src/addons/mod/workshop/assessment/comments/services/handler.ts index a8b08cc8f..9cceb5ef4 100644 --- a/src/addons/mod/workshop/assessment/comments/services/handler.ts +++ b/src/addons/mod/workshop/assessment/comments/services/handler.ts @@ -18,6 +18,7 @@ import { ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_COMMENTS_NAME, ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_COMMENTS_STRATEGY_NAME, } from '@addons/mod/workshop/assessment/constants'; +import type { AddonModWorkshopAssessmentStrategyCommentsHandlerLazyService } from './handler-lazy'; export class AddonModWorkshopAssessmentStrategyCommentsHandlerService { @@ -32,13 +33,23 @@ export class AddonModWorkshopAssessmentStrategyCommentsHandlerService { * @returns Assessment strategy handler. */ export function getAssessmentStrategyHandlerInstance(): AddonWorkshopAssessmentStrategyHandler { - const lazyHandler = asyncInstance(async () => { + const lazyHandler = asyncInstance< + AddonModWorkshopAssessmentStrategyCommentsHandlerLazyService, + AddonModWorkshopAssessmentStrategyCommentsHandlerService + >(async () => { const { AddonModWorkshopAssessmentStrategyCommentsHandler } = await import('./handler-lazy'); return AddonModWorkshopAssessmentStrategyCommentsHandler.instance; }); lazyHandler.setEagerInstance(new AddonModWorkshopAssessmentStrategyCommentsHandlerService()); + lazyHandler.setLazyInstanceMethods([ + 'isEnabled', + 'getComponent', + 'getOriginalValues', + 'hasDataChanged', + 'prepareAssessmentData', + ]); return lazyHandler; } diff --git a/src/addons/mod/workshop/assessment/numerrors/numerrors.module.ts b/src/addons/mod/workshop/assessment/numerrors/numerrors.module.ts index 9a4f7d42b..3fdcc9df2 100644 --- a/src/addons/mod/workshop/assessment/numerrors/numerrors.module.ts +++ b/src/addons/mod/workshop/assessment/numerrors/numerrors.module.ts @@ -15,9 +15,7 @@ import { CoreSharedModule } from '@/core/shared.module'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate'; -import { - AddonModWorkshopAssessmentStrategyNumErrorsHandler, -} from '@addons/mod/workshop/assessment/numerrors/services/handler-lazy'; +import { getAssessmentStrategyHandlerInstance } from './services/handler'; @NgModule({ imports: [ @@ -28,12 +26,7 @@ import { provide: APP_INITIALIZER, multi: true, useValue: () => { - // TODO use async instances - // AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); - - AddonWorkshopAssessmentStrategyDelegate.registerHandler( - AddonModWorkshopAssessmentStrategyNumErrorsHandler.instance, - ); + AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); }, }, ], diff --git a/src/addons/mod/workshop/assessment/numerrors/services/handler.ts b/src/addons/mod/workshop/assessment/numerrors/services/handler.ts index 7e946a41f..74f40eb58 100644 --- a/src/addons/mod/workshop/assessment/numerrors/services/handler.ts +++ b/src/addons/mod/workshop/assessment/numerrors/services/handler.ts @@ -18,6 +18,7 @@ import { ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_NUMERRORS_NAME, ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_NUMERRORS_STRATEGY_NAME, } from '@addons/mod/workshop/assessment/constants'; +import type { AddonModWorkshopAssessmentStrategyNumErrorsHandlerLazyService } from './handler-lazy'; export class AddonModWorkshopAssessmentStrategyNumErrorsHandlerService { @@ -32,13 +33,23 @@ export class AddonModWorkshopAssessmentStrategyNumErrorsHandlerService { * @returns Assessment strategy handler. */ export function getAssessmentStrategyHandlerInstance(): AddonWorkshopAssessmentStrategyHandler { - const lazyHandler = asyncInstance(async () => { + const lazyHandler = asyncInstance< + AddonModWorkshopAssessmentStrategyNumErrorsHandlerLazyService, + AddonModWorkshopAssessmentStrategyNumErrorsHandlerService + >(async () => { const { AddonModWorkshopAssessmentStrategyNumErrorsHandler } = await import('./handler-lazy'); return AddonModWorkshopAssessmentStrategyNumErrorsHandler.instance; }); lazyHandler.setEagerInstance(new AddonModWorkshopAssessmentStrategyNumErrorsHandlerService()); + lazyHandler.setLazyInstanceMethods([ + 'isEnabled', + 'getComponent', + 'getOriginalValues', + 'hasDataChanged', + 'prepareAssessmentData', + ]); return lazyHandler; } diff --git a/src/addons/mod/workshop/assessment/rubric/rubric.module.ts b/src/addons/mod/workshop/assessment/rubric/rubric.module.ts index 54b49a975..62328560e 100644 --- a/src/addons/mod/workshop/assessment/rubric/rubric.module.ts +++ b/src/addons/mod/workshop/assessment/rubric/rubric.module.ts @@ -15,7 +15,7 @@ import { CoreSharedModule } from '@/core/shared.module'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate'; -import { AddonModWorkshopAssessmentStrategyRubricHandler } from '@addons/mod/workshop/assessment/rubric/services/handler-lazy'; +import { getAssessmentStrategyHandlerInstance } from './services/handler'; @NgModule({ imports: [ @@ -26,10 +26,7 @@ import { AddonModWorkshopAssessmentStrategyRubricHandler } from '@addons/mod/wor provide: APP_INITIALIZER, multi: true, useValue: () => { - // TODO use async instances - // AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); - - AddonWorkshopAssessmentStrategyDelegate.registerHandler(AddonModWorkshopAssessmentStrategyRubricHandler.instance); + AddonWorkshopAssessmentStrategyDelegate.registerHandler(getAssessmentStrategyHandlerInstance()); }, }, ], diff --git a/src/addons/mod/workshop/assessment/rubric/services/handler.ts b/src/addons/mod/workshop/assessment/rubric/services/handler.ts index aa45828f8..d61e32fba 100644 --- a/src/addons/mod/workshop/assessment/rubric/services/handler.ts +++ b/src/addons/mod/workshop/assessment/rubric/services/handler.ts @@ -18,6 +18,7 @@ import { ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_RUBRIC_NAME, ADDON_MOD_WORKSHOP_ASSESSMENT_STRATEGY_RUBRIC_STRATEGY_NAME, } from '@addons/mod/workshop/assessment/constants'; +import type { AddonModWorkshopAssessmentStrategyRubricHandlerLazyService } from './handler-lazy'; export class AddonModWorkshopAssessmentStrategyRubricHandlerService { @@ -32,13 +33,23 @@ export class AddonModWorkshopAssessmentStrategyRubricHandlerService { * @returns Assessment strategy handler. */ export function getAssessmentStrategyHandlerInstance(): AddonWorkshopAssessmentStrategyHandler { - const lazyHandler = asyncInstance(async () => { + const lazyHandler = asyncInstance< + AddonModWorkshopAssessmentStrategyRubricHandlerLazyService, + AddonModWorkshopAssessmentStrategyRubricHandlerService + >(async () => { const { AddonModWorkshopAssessmentStrategyRubricHandler } = await import('./handler-lazy'); return AddonModWorkshopAssessmentStrategyRubricHandler.instance; }); lazyHandler.setEagerInstance(new AddonModWorkshopAssessmentStrategyRubricHandlerService()); + lazyHandler.setLazyInstanceMethods([ + 'isEnabled', + 'getComponent', + 'getOriginalValues', + 'hasDataChanged', + 'prepareAssessmentData', + ]); return lazyHandler; } diff --git a/src/addons/mod/workshop/services/handlers/prefetch.ts b/src/addons/mod/workshop/services/handlers/prefetch.ts index c4c8ef95a..5e8fbf422 100644 --- a/src/addons/mod/workshop/services/handlers/prefetch.ts +++ b/src/addons/mod/workshop/services/handlers/prefetch.ts @@ -21,6 +21,7 @@ import { } from '@addons/mod/workshop/constants'; import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler'; import { CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate'; +import type { AddonModWorkshopPrefetchHandlerLazyService } from './prefetch-lazy'; export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase { @@ -37,13 +38,17 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr * @returns Prefetch handler. */ export function getPrefetchHandlerInstance(): CoreCourseModulePrefetchHandler { - const lazyHandler = asyncInstance(async () => { + const lazyHandler = asyncInstance< + AddonModWorkshopPrefetchHandlerLazyService, + AddonModWorkshopPrefetchHandlerService + >(async () => { const { AddonModWorkshopPrefetchHandler } = await import('./prefetch-lazy'); return AddonModWorkshopPrefetchHandler.instance; }); lazyHandler.setEagerInstance(new AddonModWorkshopPrefetchHandlerService()); + lazyHandler.setLazyInstanceMethods(['sync']); return lazyHandler; } diff --git a/src/addons/mod/workshop/services/handlers/sync-cron.ts b/src/addons/mod/workshop/services/handlers/sync-cron.ts index 11cf1cc1a..ea7181649 100644 --- a/src/addons/mod/workshop/services/handlers/sync-cron.ts +++ b/src/addons/mod/workshop/services/handlers/sync-cron.ts @@ -15,6 +15,7 @@ import { asyncInstance } from '@/core/utils/async-instance'; import { ADDON_MOD_WORKSHOP_SYNC_CRON_NAME } from '@addons/mod/workshop/constants'; import { CoreCronHandler } from '@services/cron'; +import type { AddonModWorkshopSyncCronHandlerLazyService } from './sync-cron-lazy'; export class AddonModWorkshopSyncCronHandlerService { @@ -28,13 +29,17 @@ export class AddonModWorkshopSyncCronHandlerService { * @returns Cron handler. */ export function getCronHandlerInstance(): CoreCronHandler { - const lazyHandler = asyncInstance(async () => { + const lazyHandler = asyncInstance< + AddonModWorkshopSyncCronHandlerLazyService, + AddonModWorkshopSyncCronHandlerService + >(async () => { const { AddonModWorkshopSyncCronHandler } = await import('./sync-cron-lazy'); return AddonModWorkshopSyncCronHandler.instance; }); lazyHandler.setEagerInstance(new AddonModWorkshopSyncCronHandlerService()); + lazyHandler.setLazyInstanceMethods(['execute', 'getInterval']); return lazyHandler; } diff --git a/src/addons/mod/workshop/tests/behat/basic_usage.feature b/src/addons/mod/workshop/tests/behat/basic_usage.feature index 2fb469bce..080395142 100644 --- a/src/addons/mod/workshop/tests/behat/basic_usage.feature +++ b/src/addons/mod/workshop/tests/behat/basic_usage.feature @@ -114,3 +114,11 @@ Feature: Test basic usage of workshop activity in app And I pull to refresh in the app Then I should find "Closed" in the app And I should find "Conclusion 1" in the app + + Scenario: Prefetch a workshop + Given I entered the workshop activity "workshop" on course "Course 1" as "teacher1" in the app + When I press "Information" in the app + And I press "Download" in the app + And I press "Close" in the app + And I press the back button in the app + Then I should find "Downloaded" in the app diff --git a/src/addons/mod/workshop/workshop.module.ts b/src/addons/mod/workshop/workshop.module.ts index ee1bda50d..110658fa8 100644 --- a/src/addons/mod/workshop/workshop.module.ts +++ b/src/addons/mod/workshop/workshop.module.ts @@ -27,8 +27,8 @@ import { AddonModWorkshopIndexLinkHandler } from './services/handlers/index-link import { AddonModWorkshopListLinkHandler } from './services/handlers/list-link'; import { AddonModWorkshopModuleHandler } from './services/handlers/module'; import { ADDON_MOD_WORKSHOP_COMPONENT, ADDON_MOD_WORKSHOP_PAGE_NAME } from '@addons/mod/workshop/constants'; -import { AddonModWorkshopPrefetchHandler } from '@addons/mod/workshop/services/handlers/prefetch-lazy'; -import { AddonModWorkshopSyncCronHandler } from '@addons/mod/workshop/services/handlers/sync-cron-lazy'; +import { getPrefetchHandlerInstance } from '@addons/mod/workshop/services/handlers/prefetch'; +import { getCronHandlerInstance } from '@addons/mod/workshop/services/handlers/sync-cron'; /** * Get workshop services. @@ -85,13 +85,10 @@ const routes: Routes = [ provide: APP_INITIALIZER, multi: true, useValue: () => { - // TODO use async instances - // CoreCourseModulePrefetchDelegate.registerHandler(getPrefetchHandlerInstance()); - // CoreCronDelegate.register(getCronHandlerInstance()); + CoreCourseModulePrefetchDelegate.registerHandler(getPrefetchHandlerInstance()); + CoreCronDelegate.register(getCronHandlerInstance()); CoreCourseModuleDelegate.registerHandler(AddonModWorkshopModuleHandler.instance); - CoreCourseModulePrefetchDelegate.registerHandler(AddonModWorkshopPrefetchHandler.instance); - CoreCronDelegate.register(AddonModWorkshopSyncCronHandler.instance); CoreContentLinksDelegate.registerHandler(AddonModWorkshopIndexLinkHandler.instance); CoreContentLinksDelegate.registerHandler(AddonModWorkshopListLinkHandler.instance); diff --git a/src/core/utils/async-instance.ts b/src/core/utils/async-instance.ts index 088ff66b0..b12b645d5 100644 --- a/src/core/utils/async-instance.ts +++ b/src/core/utils/async-instance.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { TupleMatches } from '@/core/utils/types'; import { CorePromisedValue } from '@classes/promised-value'; /** @@ -27,12 +28,16 @@ function createAsyncInstanceWrapper< lazyConstructor?: () => TLazyInstance | Promise, ): AsyncInstanceWrapper { let promisedInstance: CorePromisedValue | null = null; + let lazyInstanceMethods: Array; let eagerInstance: TEagerInstance; return { get instance() { return promisedInstance?.value ?? undefined; }, + get lazyInstanceMethods() { + return lazyInstanceMethods; + }, get eagerInstance() { return eagerInstance; }, @@ -63,6 +68,9 @@ function createAsyncInstanceWrapper< promisedInstance.resolve(instance); }, + setLazyInstanceMethods(methods) { + lazyInstanceMethods = methods; + }, setEagerInstance(instance) { eagerInstance = instance; }, @@ -108,10 +116,14 @@ export interface AsyncInstanceWrapper< TEagerInstance extends AsyncObject = Partial > { instance?: TLazyInstance; + lazyInstanceMethods?: Array; eagerInstance?: TEagerInstance; getInstance(): Promise; getProperty

(property: P): Promise; setInstance(instance: TLazyInstance): void; + setLazyInstanceMethods>( + methods: LazyMethodsGuard, + ): void; setEagerInstance(eagerInstance: TEagerInstance): void; setLazyConstructor(lazyConstructor: () => TLazyInstance | Promise): void; resetInstance(): void; @@ -141,6 +153,12 @@ export type AsyncInstance; }; +/** + * Guard type to make sure that lazy methods match what the lazy class implements. + */ +export type LazyMethodsGuard, TLazyInstance, TEagerInstance> = + TupleMatches> extends true ? TMethods : never; + /** * Create an asynchronous instance proxy, where all methods will be callable directly but will become asynchronous. If the * underlying instance hasn't been set, methods will be resolved once it is. @@ -171,6 +189,10 @@ export function asyncInstance { const instance = await wrapper.getInstance(); const method = Reflect.get(instance, property, receiver); diff --git a/src/core/utils/tests/async-instance.test.ts b/src/core/utils/tests/async-instance.test.ts index c17f29b68..cf5b41657 100644 --- a/src/core/utils/tests/async-instance.test.ts +++ b/src/core/utils/tests/async-instance.test.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance'; +import { AsyncInstance, LazyMethodsGuard, asyncInstance } from '@/core/utils/async-instance'; import { expectAnyType, expectSameTypes } from '@/testing/utils'; describe('AsyncInstance', () => { @@ -33,8 +33,45 @@ describe('AsyncInstance', () => { expect(asyncService.instance).toBeUndefined(); expect(asyncService.answer).toEqual(42); expect(asyncService.instance).toBeUndefined(); + expect(await asyncService.isEager()).toBe(true); expect(await asyncService.hello()).toEqual('Hi there!'); expect(asyncService.instance).toBeInstanceOf(LazyService); + expect(await asyncService.isEager()).toBe(false); + }); + + it('does not return undefined methods when they are declared', async () => { + const asyncService = asyncInstance(() => new LazyService()); + + asyncService.setEagerInstance(new EagerService()); + asyncService.setLazyInstanceMethods(['hello', 'goodbye']); + + expect(asyncService.hello).not.toBeUndefined(); + expect(asyncService.goodbye).not.toBeUndefined(); + expect(asyncService.isEager).not.toBeUndefined(); + expect(asyncService.notImplemented).toBeUndefined(); + }); + + it('guards against missing or invalid instance methods', () => { + // Define interfaces. + interface Eager { + lorem(): void; + ipsum(): void; + } + + interface Lazy extends Eager { + foo(): void; + bar(): void; + } + + // Test valid method tuples. + expectSameTypes, ['foo', 'bar']>(true); + expectSameTypes, ['bar', 'foo']>(true); + expectSameTypes, ['foo', 'foo', 'bar']>(true); + + // Test invalid method tuples. + expectSameTypes, never>(true); + expectSameTypes, never>(true); + expectSameTypes, never>(true); }); it('preserves undefined properties after initialization', async () => { @@ -73,6 +110,12 @@ class EagerService { answer = 42; + notImplemented?(): void; + + async isEager(): Promise { + return true; + } + } class FakeEagerService { @@ -83,6 +126,10 @@ class FakeEagerService { class LazyService extends EagerService { + async isEager(): Promise { + return false; + } + hello(): string { return 'Hi there!'; } diff --git a/src/core/utils/types.ts b/src/core/utils/types.ts index bb4b85917..8241d0bd6 100644 --- a/src/core/utils/types.ts +++ b/src/core/utils/types.ts @@ -39,6 +39,47 @@ export type Pretty = T extends infer U ? {[K in keyof U]: U[K]} : never; */ export type SubPartial = Omit & Partial>; +/** + * Helper type to negate a boolean type. + */ +export type Not = IsTrue extends true ? false : (IsFalse extends true ? true : boolean); + +/** + * Helper type to check whether a boolean type is exactly `true`. + */ +export type IsTrue = Exclude extends never ? true : false; + +/** + * Helper type to check whether a boolean type is exactly `false`. + */ +export type IsFalse = Exclude extends never ? true : false; + +/** + * Helper type to check whether the given tuple contains all the items in a union. + */ +export type TupleContainsAll = Exclude< + TItems, + TTuple[number] +> extends never ? true : false; + +/** + * Helper type to check whether the given tuple contains any items outside of a union. + */ +export type TupleContainsOthers = Exclude< + TTuple[number], + TItems +> extends never ? false : true; + +/** + * Helper type to check whether the given tuple matches the items in a union. + * + * This means that the tuple will have all the items from the union, but not any outside of it. + */ +export type TupleMatches = IsTrue< + TupleContainsAll | + Not> +>; + /** * Helper type to omit union. * You can use it if need to omit an element from types union.