diff --git a/CHANGELOG.md b/CHANGELOG.md index f8585b59..f9900bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 10.0.0-alpha.1 (2023-02-29) + +- Add ability to mark features as being in preview. [#272](https://github.com/blackbaud/skyux-docs-tools/pull/272) [#273](https://github.com/blackbaud/skyux-docs-tools/pull/273) + +## 9.2.0 (2023-02-29) + +- Add ability to mark features as being in preview. [#272](https://github.com/blackbaud/skyux-docs-tools/pull/272) + ## 10.0.0-alpha.0 (2024-01-30) ### ⚠ BREAKING CHANGES diff --git a/projects/docs-tools/package.json b/projects/docs-tools/package.json index ac86855a..70ec80f9 100644 --- a/projects/docs-tools/package.json +++ b/projects/docs-tools/package.json @@ -1,6 +1,6 @@ { "name": "@skyux/docs-tools", - "version": "10.0.0-alpha.0", + "version": "10.0.0-alpha.1", "peerDependencies": { "@angular/common": "^17.1.1", "@angular/core": "^17.1.1", diff --git a/projects/docs-tools/src/modules/demo-page/demo-page.component.html b/projects/docs-tools/src/modules/demo-page/demo-page.component.html index 15001ba1..5b69c9e5 100644 --- a/projects/docs-tools/src/modules/demo-page/demo-page.component.html +++ b/projects/docs-tools/src/modules/demo-page/demo-page.component.html @@ -8,6 +8,22 @@ > + This component currently has preview features available that may not be + fully represented in the design documentation, the demo, or code examples. + Learn more about this work + here. +
diff --git a/projects/docs-tools/src/modules/demo-page/demo-page.component.spec.ts b/projects/docs-tools/src/modules/demo-page/demo-page.component.spec.ts index 0b1e72c6..77b8a00a 100644 --- a/projects/docs-tools/src/modules/demo-page/demo-page.component.spec.ts +++ b/projects/docs-tools/src/modules/demo-page/demo-page.component.spec.ts @@ -19,6 +19,16 @@ import { SkyDocsTypeDefinitionsService } from '../type-definitions/type-definiti import { MockTypeDocAdapterService } from '../type-definitions/fixtures/mock-type-definitions.service'; import { SkyDocsTypeDocAdapterService } from '../type-definitions/typedoc-adapter.service'; import { TypeDocKind } from '../type-definitions/typedoc-types'; +import { SkyDocsToolsSiteOptions } from '../shared/docs-tools-site-options'; + +function getPreviewAlert(): HTMLElement | null { + return document.querySelector('.sky-docs-demo-page-preview-alert'); +} + +function getPreviewAlertLink(): HTMLAnchorElement | null { + return getPreviewAlert().querySelector('a'); +} + function getService( provider: SkyDocsTypeDefinitionsProvider = { anchorIds: {}, @@ -181,7 +191,7 @@ describe('Demo page component', () => { let component: DemoPageFixtureComponent; let mockMediaQueryService: MockSkyMediaQueryService; - beforeEach(() => { + function setupTestBed(): void { mockMediaQueryService = new MockSkyMediaQueryService(); TestBed.configureTestingModule({ imports: [DemoPageFixturesModule], @@ -191,8 +201,25 @@ describe('Demo page component', () => { useValue: mockMediaQueryService, }, { provide: SkyDocsTypeDefinitionsService, useValue: getService() }, + { + provide: SkyDocsTypeDefinitionsProvider, + useValue: { + anchorIds: { + FooUser: 'foo-user', + }, + typeDefinitions: [ + { + name: 'FooUser', + }, + ], + }, + }, ], }); + } + + beforeEach(() => { + setupTestBed(); fixture = TestBed.createComponent(DemoPageFixtureComponent); component = fixture.componentInstance; @@ -321,4 +348,54 @@ describe('Demo page component', () => { ); expect(sidebarLinks[1].getAttribute('href')).toEqual('/bar'); }); + + it('should not show the preview features alert if there are no preview features', () => { + fixture.detectChanges(); + const previewAlert = getPreviewAlert(); + expect(previewAlert).toBeNull(); + }); + + it('should show the preview features alert if there are preview features - no link', () => { + spyOn( + MockTypeDocAdapterService.prototype, + 'toClassDefinition' + ).and.callFake((entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + name: entry.name, + }; + }); + fixture.detectChanges(); + const previewAlert = getPreviewAlert(); + const previewAlertLink = getPreviewAlertLink(); + expect(previewAlert).not.toBeNull(); + expect(previewAlertLink).toBeNull(); + }); + + it('should show the preview features alert if there are preview features - with a link when provided by the consumer', () => { + TestBed.resetTestingModule(); + TestBed.overrideProvider(SkyDocsToolsSiteOptions, { + useValue: { previewFeaturesUrl: 'www.blackbaud.com' }, + }); + setupTestBed(); + + fixture = TestBed.createComponent(DemoPageFixtureComponent); + component = fixture.componentInstance; + spyOn( + MockTypeDocAdapterService.prototype, + 'toClassDefinition' + ).and.callFake((entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + name: entry.name, + }; + }); + fixture.detectChanges(); + const previewAlert = getPreviewAlert(); + const previewAlertLink = getPreviewAlertLink(); + expect(previewAlert).not.toBeNull(); + expect(previewAlertLink).not.toBeNull(); + }); }); diff --git a/projects/docs-tools/src/modules/demo-page/demo-page.component.ts b/projects/docs-tools/src/modules/demo-page/demo-page.component.ts index 425985a2..a1f65b6c 100644 --- a/projects/docs-tools/src/modules/demo-page/demo-page.component.ts +++ b/projects/docs-tools/src/modules/demo-page/demo-page.component.ts @@ -9,6 +9,7 @@ import { Input, OnInit, QueryList, + inject, } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; @@ -30,6 +31,7 @@ import { SkyDocsDemoPageDomAdapterService } from './demo-page-dom-adapter.servic import { SkyDocsDemoPageTitleService } from './demo-page-title.service'; import { SkyDocsTypeDefinitionsService } from '../type-definitions/type-definitions.service'; import { SkyDocsTypeDefinitions } from '../type-definitions/type-definitions'; +import { SkyDocsToolsSiteOptions } from '../shared/docs-tools-site-options'; /** * The demo page component wraps all documentation components and handles the configuration and layout of the page. @@ -161,12 +163,19 @@ export class SkyDocsDemoPageComponent */ public sidebarRoutes: StacheNavLink[]; + protected hasPreviewFeatures = false; + @ContentChild(SkyDocsDesignGuidelinesComponent) private designGuidelinesComponent: SkyDocsDesignGuidelinesComponent; @ContentChildren(SkyDocsCodeExamplesComponent) private codeExampleComponents: QueryList; + protected readonly siteOptions: SkyDocsToolsSiteOptions | undefined = inject( + SkyDocsToolsSiteOptions, + { optional: true } + ); + #_additionalSourceCodePaths: string[] | undefined; #_additionalTestingSourceCodePaths: string[] | undefined; #_moduleSourceCodePath: string | undefined; diff --git a/projects/docs-tools/src/modules/demo-page/demo-page.module.ts b/projects/docs-tools/src/modules/demo-page/demo-page.module.ts index 10030251..2645ca3f 100644 --- a/projects/docs-tools/src/modules/demo-page/demo-page.module.ts +++ b/projects/docs-tools/src/modules/demo-page/demo-page.module.ts @@ -35,11 +35,15 @@ import { SkyDocsDemoPageComponent } from './demo-page.component'; import { SkyDocsDemoPageModuleInfoComponent } from './demo-page-module-info.component'; import { SkyDocsDemoPageTypeDefinitionsComponent } from './demo-page-type-definitions.component'; +import { SkyAlertModule } from '@skyux/indicators'; +import { SkyAppLinkModule } from '@skyux/router'; @NgModule({ imports: [ CommonModule, RouterModule, + SkyAlertModule, + SkyAppLinkModule, SkyCodeBlockModule, SkyCodeModule, SkyDocsDemoModule, diff --git a/projects/docs-tools/src/modules/demo-page/fixtures/demo-page-fixtures.module.ts b/projects/docs-tools/src/modules/demo-page/fixtures/demo-page-fixtures.module.ts index d949c8ae..898eb2ba 100644 --- a/projects/docs-tools/src/modules/demo-page/fixtures/demo-page-fixtures.module.ts +++ b/projects/docs-tools/src/modules/demo-page/fixtures/demo-page-fixtures.module.ts @@ -35,6 +35,9 @@ class MockSkyAppConfig { base: '/demo-test-base/', }, routes: [], + params: { + getAll: (_: unknown) => {}, + }, }; public skyux: any = { diff --git a/projects/docs-tools/src/modules/shared/docs-tools-site-options.ts b/projects/docs-tools/src/modules/shared/docs-tools-site-options.ts new file mode 100644 index 00000000..1df2be71 --- /dev/null +++ b/projects/docs-tools/src/modules/shared/docs-tools-site-options.ts @@ -0,0 +1,3 @@ +export class SkyDocsToolsSiteOptions { + public previewFeaturesUrl: string; +} diff --git a/projects/docs-tools/src/modules/type-definitions/class-definition.component.spec.ts b/projects/docs-tools/src/modules/type-definitions/class-definition.component.spec.ts index b7fcbfb4..053ff2c2 100644 --- a/projects/docs-tools/src/modules/type-definitions/class-definition.component.spec.ts +++ b/projects/docs-tools/src/modules/type-definitions/class-definition.component.spec.ts @@ -54,6 +54,7 @@ describe('Class definition component', function () { anchorId: 'service-fooservice', description: 'This description has a FooUser.', name: 'FooService', + hasPreviewFeatures: false, }; fixture.detectChanges(); diff --git a/projects/docs-tools/src/modules/type-definitions/class-definition.ts b/projects/docs-tools/src/modules/type-definitions/class-definition.ts index 57f489da..6e599b97 100644 --- a/projects/docs-tools/src/modules/type-definitions/class-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/class-definition.ts @@ -8,6 +8,8 @@ import { SkyDocsClassMethodDefinition } from './method-definition'; * Describes classes and services. */ export interface SkyDocsClassDefinition extends SkyDocsEntryDefinition { + hasPreviewFeatures: boolean; + methods?: SkyDocsClassMethodDefinition[]; properties?: SkyDocsClassPropertyDefinition[]; diff --git a/projects/docs-tools/src/modules/type-definitions/directive-definition.component.spec.ts b/projects/docs-tools/src/modules/type-definitions/directive-definition.component.spec.ts index 91ea9710..685cb92a 100644 --- a/projects/docs-tools/src/modules/type-definitions/directive-definition.component.spec.ts +++ b/projects/docs-tools/src/modules/type-definitions/directive-definition.component.spec.ts @@ -61,6 +61,7 @@ describe('Directive definition component', function () { anchorId: 'foo-anchor-id', name: 'FooComponent', selector: 'app-foo', + hasPreviewFeatures: false, }; fixture.detectChanges(); @@ -77,6 +78,7 @@ describe('Directive definition component', function () { anchorId: 'foo-anchor-id', name: 'FooComponent', selector: 'app-foo', + hasPreviewFeatures: false, inputProperties: [ { name: 'config', @@ -84,6 +86,7 @@ describe('Directive definition component', function () { name: 'Input', }, isOptional: true, + isPreview: false, type: { type: 'reference', name: 'Config', @@ -97,6 +100,7 @@ describe('Directive definition component', function () { name: 'Output', }, isOptional: true, + isPreview: false, type: { type: 'reference', name: 'EventEmitter', @@ -130,6 +134,7 @@ describe('Directive definition component', function () { anchorId: 'foo-anchor-id', name: 'FooComponent', selector: 'app-foo', + hasPreviewFeatures: false, eventProperties: [ { name: 'click', @@ -137,6 +142,7 @@ describe('Directive definition component', function () { name: 'Output', }, isOptional: false, + isPreview: false, type: {}, }, ], @@ -156,6 +162,7 @@ describe('Directive definition component', function () { anchorId: 'component-foocomponent', name: 'FooComponent', description: 'This description has a [[FooUser]].', + hasPreviewFeatures: false, selector: 'app-foo', }; diff --git a/projects/docs-tools/src/modules/type-definitions/directive-definition.ts b/projects/docs-tools/src/modules/type-definitions/directive-definition.ts index 860a62d9..30313764 100644 --- a/projects/docs-tools/src/modules/type-definitions/directive-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/directive-definition.ts @@ -8,6 +8,8 @@ import { SkyDocsEntryDefinition } from './entry-definition'; export interface SkyDocsDirectiveDefinition extends SkyDocsEntryDefinition { eventProperties?: SkyDocsClassPropertyDefinition[]; + hasPreviewFeatures: boolean; + inputProperties?: SkyDocsClassPropertyDefinition[]; selector: string; diff --git a/projects/docs-tools/src/modules/type-definitions/enumeration-definition.component.spec.ts b/projects/docs-tools/src/modules/type-definitions/enumeration-definition.component.spec.ts index e67283e7..4495bd9e 100644 --- a/projects/docs-tools/src/modules/type-definitions/enumeration-definition.component.spec.ts +++ b/projects/docs-tools/src/modules/type-definitions/enumeration-definition.component.spec.ts @@ -66,9 +66,11 @@ describe('Enumeration definition component', function () { anchorId: 'foo-anchor-id', name: 'Foo', description: 'This description has a FooUser.', + hasPreviewFeatures: false, members: [ { name: 'Bar', + isPreview: false, }, ], }; @@ -90,9 +92,11 @@ describe('Enumeration definition component', function () { anchorId: 'foo-anchor-id', name: 'Foo', description: 'This description has a `Date`.', + hasPreviewFeatures: false, members: [ { name: 'Bar', + isPreview: false, }, ], }; @@ -112,10 +116,12 @@ describe('Enumeration definition component', function () { anchorId: 'foo-anchor-id', name: 'Foo', description: '', + hasPreviewFeatures: false, members: [ { description: 'This description has a FooUser.', name: 'Bar', + isPreview: false, }, ], }; diff --git a/projects/docs-tools/src/modules/type-definitions/enumeration-definition.ts b/projects/docs-tools/src/modules/type-definitions/enumeration-definition.ts index f69fbd17..c9bb8352 100644 --- a/projects/docs-tools/src/modules/type-definitions/enumeration-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/enumeration-definition.ts @@ -6,5 +6,6 @@ import { SkyDocsEnumerationMemberDefinition } from './enumeration-member-definit * Describes enumerations. */ export interface SkyDocsEnumerationDefinition extends SkyDocsEntryDefinition { + hasPreviewFeatures: boolean; members: SkyDocsEnumerationMemberDefinition[]; } diff --git a/projects/docs-tools/src/modules/type-definitions/enumeration-member-definition.ts b/projects/docs-tools/src/modules/type-definitions/enumeration-member-definition.ts index 9134ddaa..ea532067 100644 --- a/projects/docs-tools/src/modules/type-definitions/enumeration-member-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/enumeration-member-definition.ts @@ -10,5 +10,7 @@ export interface SkyDocsEnumerationMemberDefinition { description?: string; + isPreview: boolean; + name: string; } diff --git a/projects/docs-tools/src/modules/type-definitions/fixtures/mock-type-definitions.service.ts b/projects/docs-tools/src/modules/type-definitions/fixtures/mock-type-definitions.service.ts index 48e01772..a9cbb152 100644 --- a/projects/docs-tools/src/modules/type-definitions/fixtures/mock-type-definitions.service.ts +++ b/projects/docs-tools/src/modules/type-definitions/fixtures/mock-type-definitions.service.ts @@ -9,12 +9,14 @@ import { SkyDocsInterfaceDefinition } from '../interface-definition'; import { SkyDocsPipeDefinition } from '../pipe-definition'; import { SkyDocsTypeAliasDefinition } from '../type-alias-definition'; +import { SkyDocsTypeDocAdapterService } from '../typedoc-adapter.service'; import { TypeDocEntry } from '../typedoc-types'; -export class MockTypeDocAdapterService { +export class MockTypeDocAdapterService extends SkyDocsTypeDocAdapterService { public toClassDefinition(entry: TypeDocEntry): SkyDocsClassDefinition { return { + hasPreviewFeatures: false, anchorId: entry.anchorId, name: entry.name, }; @@ -24,6 +26,7 @@ export class MockTypeDocAdapterService { entry: TypeDocEntry ): SkyDocsDirectiveDefinition { return { + hasPreviewFeatures: false, anchorId: entry.anchorId, name: entry.name, selector: 'foo', @@ -34,6 +37,7 @@ export class MockTypeDocAdapterService { entry: TypeDocEntry ): SkyDocsEnumerationDefinition { return { + hasPreviewFeatures: false, anchorId: entry.anchorId, members: undefined, name: entry.name, @@ -44,6 +48,7 @@ export class MockTypeDocAdapterService { entry: TypeDocEntry ): SkyDocsInterfaceDefinition { return { + hasPreviewFeatures: false, anchorId: entry.anchorId, name: entry.name, properties: [], @@ -54,7 +59,17 @@ export class MockTypeDocAdapterService { return { anchorId: entry.anchorId, name: entry.name, - transformMethod: undefined, + transformMethod: { + name: 'transform', + isPreview: false, + type: { + callSignature: { + returnType: { + name: 'stringj', + }, + }, + }, + }, }; } @@ -64,6 +79,7 @@ export class MockTypeDocAdapterService { return { anchorId: entry.anchorId, name: entry.name, + isPreview: false, type: {}, }; } diff --git a/projects/docs-tools/src/modules/type-definitions/interface-definition.component.spec.ts b/projects/docs-tools/src/modules/type-definitions/interface-definition.component.spec.ts index d02a1904..b03b3bce 100644 --- a/projects/docs-tools/src/modules/type-definitions/interface-definition.component.spec.ts +++ b/projects/docs-tools/src/modules/type-definitions/interface-definition.component.spec.ts @@ -66,9 +66,11 @@ describe('Interface definition component', function () { anchorId: 'foo-anchor-id', name: 'Foo', description: 'This description has a FooUser.', + hasPreviewFeatures: false, properties: [ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -95,10 +97,12 @@ describe('Interface definition component', function () { anchorId: 'foo-anchor-id', name: 'Foo', description: '', + hasPreviewFeatures: false, properties: [ { description: 'This description has a FooUser.', isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -125,9 +129,11 @@ describe('Interface definition component', function () { anchorId: 'foo-anchor-id', name: 'Foo', description: 'This description has a FooUser.', + hasPreviewFeatures: false, properties: [ { isOptional: true, + isPreview: false, name: 'foo', type: { callSignature: { diff --git a/projects/docs-tools/src/modules/type-definitions/interface-definition.ts b/projects/docs-tools/src/modules/type-definitions/interface-definition.ts index c0139c6b..a2944c31 100644 --- a/projects/docs-tools/src/modules/type-definitions/interface-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/interface-definition.ts @@ -8,6 +8,8 @@ import { SkyDocsTypeParameterDefinition } from './type-parameter-definition'; * Describes interfaces. */ export interface SkyDocsInterfaceDefinition extends SkyDocsEntryDefinition { + hasPreviewFeatures: boolean; + properties: SkyDocsInterfacePropertyDefinition[]; typeParameters?: SkyDocsTypeParameterDefinition[]; diff --git a/projects/docs-tools/src/modules/type-definitions/interface-property-definition.ts b/projects/docs-tools/src/modules/type-definitions/interface-property-definition.ts index 121e305a..d0a2ac2f 100644 --- a/projects/docs-tools/src/modules/type-definitions/interface-property-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/interface-property-definition.ts @@ -6,4 +6,5 @@ import { SkyDocsEntryChildDefinition } from './entry-child-definition'; export interface SkyDocsInterfacePropertyDefinition extends SkyDocsEntryChildDefinition { isOptional: boolean; + isPreview: boolean; } diff --git a/projects/docs-tools/src/modules/type-definitions/method-definition.ts b/projects/docs-tools/src/modules/type-definitions/method-definition.ts index 54d932b3..44b9a5b0 100644 --- a/projects/docs-tools/src/modules/type-definitions/method-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/method-definition.ts @@ -8,6 +8,7 @@ import { SkyDocsTypeParameterDefinition } from './type-parameter-definition'; export interface SkyDocsClassMethodDefinition extends SkyDocsEntryChildDefinition { isStatic?: boolean; + isPreview: boolean; typeParameters?: SkyDocsTypeParameterDefinition[]; parentName?: string; } diff --git a/projects/docs-tools/src/modules/type-definitions/method-definitions.component.html b/projects/docs-tools/src/modules/type-definitions/method-definitions.component.html index f39843c9..b149611e 100644 --- a/projects/docs-tools/src/modules/type-definitions/method-definitions.component.html +++ b/projects/docs-tools/src/modules/type-definitions/method-definitions.component.html @@ -138,6 +138,13 @@ +
+ +
+ +): NodeListOf { + return (fixture.nativeElement as HTMLElement).querySelectorAll( + '.sky-docs-standard-method-definitions .sky-docs-method-definition-preview-warning' + ); +} + describe('Method definitions component', function () { let fixture: ComponentFixture; @@ -56,6 +64,7 @@ describe('Method definitions component', function () { name: 'FooMethod', description: 'This description has a FooUser.', isStatic: true, + isPreview: false, type: { callSignature: { returnType: { @@ -86,12 +95,69 @@ describe('Method definitions component', function () { expect(methodElement.innerText).toContain('public static '); })); + it('should mark a method as preview', fakeAsync(() => { + fixture.componentInstance.config = { + methods: [ + { + name: 'FooMethod', + description: 'This description has a FooUser.', + isStatic: false, + isPreview: true, + type: { + callSignature: { + returnType: { + type: 'reference', + name: 'FooUser', + }, + }, + }, + }, + ], + }; + + fixture.detectChanges(); + tick(); + + const previewWarnings = getPreviewWarnings(fixture); + + expect(previewWarnings.length).toBe(1); + })); + + it('should not mark a method as preview when not in preview', fakeAsync(() => { + fixture.componentInstance.config = { + methods: [ + { + name: 'FooMethod', + description: 'This description has a FooUser.', + isStatic: false, + isPreview: false, + type: { + callSignature: { + returnType: { + type: 'reference', + name: 'FooUser', + }, + }, + }, + }, + ], + }; + + fixture.detectChanges(); + tick(); + + const previewWarnings = getPreviewWarnings(fixture); + + expect(previewWarnings.length).toBe(0); + })); + it('should add links to types within description', fakeAsync(() => { fixture.componentInstance.config = { methods: [ { name: 'FooMethod', description: 'This description has a FooUser.', + isPreview: false, type: { callSignature: { returnType: { @@ -122,6 +188,7 @@ describe('Method definitions component', function () { { name: 'FooMethod', deprecationWarning: 'This description has a FooUser.', + isPreview: false, type: { callSignature: { returnType: { diff --git a/projects/docs-tools/src/modules/type-definitions/method-definitions.component.ts b/projects/docs-tools/src/modules/type-definitions/method-definitions.component.ts index c8ba0aa2..c8eb8605 100644 --- a/projects/docs-tools/src/modules/type-definitions/method-definitions.component.ts +++ b/projects/docs-tools/src/modules/type-definitions/method-definitions.component.ts @@ -23,6 +23,7 @@ interface MethodViewModel { formattedName: string; sourceCode: string; isStatic: boolean; + isPreview: boolean; } @Component({ @@ -89,6 +90,7 @@ export class SkyDocsMethodDefinitionsComponent implements OnInit { formattedName: this.#formatService.getFormattedMethodName(method), sourceCode: this.#formatService.getMethodSourceCode(method), isStatic: method.isStatic, + isPreview: method.isPreview, }; if (vm.isStatic) { diff --git a/projects/docs-tools/src/modules/type-definitions/preview-feature-indicator.component.html b/projects/docs-tools/src/modules/type-definitions/preview-feature-indicator.component.html new file mode 100644 index 00000000..68d2905e --- /dev/null +++ b/projects/docs-tools/src/modules/type-definitions/preview-feature-indicator.component.html @@ -0,0 +1,4 @@ +Preview. This feature may not be fully represented in the design + documentation, the demo, or code examples. diff --git a/projects/docs-tools/src/modules/type-definitions/preview-feature-indicator.component.ts b/projects/docs-tools/src/modules/type-definitions/preview-feature-indicator.component.ts new file mode 100644 index 00000000..aca64e1a --- /dev/null +++ b/projects/docs-tools/src/modules/type-definitions/preview-feature-indicator.component.ts @@ -0,0 +1,12 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +import { SkyStatusIndicatorModule } from '@skyux/indicators'; + +@Component({ + selector: 'sky-docs-preview-feature-indicator', + templateUrl: './preview-feature-indicator.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [SkyStatusIndicatorModule], +}) +export class SkyDocsPreviewIndicatorComponent {} diff --git a/projects/docs-tools/src/modules/type-definitions/property-definition.ts b/projects/docs-tools/src/modules/type-definitions/property-definition.ts index 0a1aac2c..0b7bdbd8 100644 --- a/projects/docs-tools/src/modules/type-definitions/property-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/property-definition.ts @@ -12,4 +12,6 @@ export interface SkyDocsClassPropertyDefinition defaultValue?: string; isOptional: boolean; + + isPreview: boolean; } diff --git a/projects/docs-tools/src/modules/type-definitions/property-definitions.component.html b/projects/docs-tools/src/modules/type-definitions/property-definitions.component.html index e9a82aaa..e1267f72 100644 --- a/projects/docs-tools/src/modules/type-definitions/property-definitions.component.html +++ b/projects/docs-tools/src/modules/type-definitions/property-definitions.component.html @@ -70,9 +70,17 @@
- Required. + Required. +
+
+
+): NodeListOf { + return (fixture.nativeElement as HTMLElement).querySelectorAll( + '.sky-docs-property-definitions .sky-docs-property-definition-preview-warning' + ); +} + describe('Property definitions component', function () { let fixture: ComponentFixture; let mockMediaQueryService: MockSkyMediaQueryService; @@ -70,6 +78,7 @@ describe('Property definitions component', function () { properties: [ { isOptional: false, + isPreview: false, decorator: { name: 'Input', }, @@ -92,11 +101,64 @@ describe('Property definitions component', function () { expect(element.textContent).toEqual('@Input()foobar: number'); })); + it('should mark a method as preview', fakeAsync(() => { + fixture.componentInstance.config = { + properties: [ + { + isOptional: false, + isPreview: true, + decorator: { + name: 'Input', + }, + name: 'foobar', + type: { + type: 'intrinsic', + name: 'number', + }, + }, + ], + }; + + fixture.detectChanges(); + tick(); + + const previewWarnings = getPreviewWarnings(fixture); + + expect(previewWarnings.length).toBe(1); + })); + + it('should not mark a method as preview when not in preview', fakeAsync(() => { + fixture.componentInstance.config = { + properties: [ + { + isOptional: false, + isPreview: false, + decorator: { + name: 'Input', + }, + name: 'foobar', + type: { + type: 'intrinsic', + name: 'number', + }, + }, + ], + }; + + fixture.detectChanges(); + tick(); + + const previewWarnings = getPreviewWarnings(fixture); + + expect(previewWarnings.length).toBe(0); + })); + it('should add anchor links to default value', fakeAsync(() => { fixture.componentInstance.config = { properties: [ { isOptional: true, + isPreview: false, decorator: { name: 'Input', }, @@ -127,6 +189,7 @@ describe('Property definitions component', function () { properties: [ { isOptional: true, + isPreview: false, decorator: { name: 'Input', }, @@ -162,6 +225,7 @@ describe('Property definitions component', function () { properties: [ { isOptional: true, + isPreview: false, decorator: { name: 'Input', }, @@ -197,6 +261,7 @@ describe('Property definitions component', function () { properties: [ { isOptional: true, + isPreview: false, name: 'foobar', type: { type: 'reference', diff --git a/projects/docs-tools/src/modules/type-definitions/property-definitions.component.ts b/projects/docs-tools/src/modules/type-definitions/property-definitions.component.ts index b61bef06..9ba55477 100644 --- a/projects/docs-tools/src/modules/type-definitions/property-definitions.component.ts +++ b/projects/docs-tools/src/modules/type-definitions/property-definitions.component.ts @@ -22,6 +22,7 @@ interface PropertyViewModel { formattedName: string; isOutput: boolean; isOptional: boolean; + isPreview: boolean; } @Component({ @@ -82,6 +83,7 @@ export class SkyDocsPropertyDefinitionsComponent implements OnInit { formattedName: this.formatService.getFormattedPropertyName(property), isOutput: property.decorator?.name === 'Output', isOptional: property.isOptional, + isPreview: property.isPreview, }; return vm; diff --git a/projects/docs-tools/src/modules/type-definitions/type-alias-definition.component.html b/projects/docs-tools/src/modules/type-definitions/type-alias-definition.component.html index 41c08e42..ac2ae47f 100644 --- a/projects/docs-tools/src/modules/type-definitions/type-alias-definition.component.html +++ b/projects/docs-tools/src/modules/type-definitions/type-alias-definition.component.html @@ -6,6 +6,13 @@ {{ config.name }} +
+ +
+ +): HTMLElement | null { + return (fixture.nativeElement as HTMLElement).querySelector( + '.sky-docs-type-alias-definition .sky-docs-type-alias-preview-warning' + ); +} + describe('Type alias definition component', function () { let fixture: ComponentFixture; @@ -55,6 +63,7 @@ describe('Type alias definition component', function () { anchorId: 'foo-anchor-id', name: 'Foo', description: 'This description has a FooUser.', + isPreview: false, type: { callSignature: { returnType: { @@ -76,4 +85,52 @@ describe('Type alias definition component', function () { 'FooUser' ); })); + + it('should mark a method as preview', fakeAsync(() => { + fixture.componentInstance.config = { + anchorId: 'foo-anchor-id', + name: 'Foo', + description: 'This description has a FooUser.', + isPreview: true, + type: { + callSignature: { + returnType: { + type: 'reference', + name: 'FooUser', + }, + }, + }, + }; + + fixture.detectChanges(); + tick(); + + const previewWarning = getPreviewWarning(fixture); + + expect(previewWarning).not.toBeNull(); + })); + + it('should not mark a method as preview when not in preview', fakeAsync(() => { + fixture.componentInstance.config = { + anchorId: 'foo-anchor-id', + name: 'Foo', + description: 'This description has a FooUser.', + isPreview: false, + type: { + callSignature: { + returnType: { + type: 'reference', + name: 'FooUser', + }, + }, + }, + }; + + fixture.detectChanges(); + tick(); + + const previewWarning = getPreviewWarning(fixture); + + expect(previewWarning).toBeNull(); + })); }); diff --git a/projects/docs-tools/src/modules/type-definitions/type-alias-definition.ts b/projects/docs-tools/src/modules/type-definitions/type-alias-definition.ts index 5e768817..729ac9d0 100644 --- a/projects/docs-tools/src/modules/type-definitions/type-alias-definition.ts +++ b/projects/docs-tools/src/modules/type-definitions/type-alias-definition.ts @@ -8,6 +8,8 @@ import { SkyDocsTypeParameterDefinition } from './type-parameter-definition'; * Describes type aliases. */ export interface SkyDocsTypeAliasDefinition extends SkyDocsEntryDefinition { + isPreview: boolean; + type: SkyDocsTypeDefinition; typeParameters?: SkyDocsTypeParameterDefinition[]; diff --git a/projects/docs-tools/src/modules/type-definitions/type-definitions-format.service.spec.ts b/projects/docs-tools/src/modules/type-definitions/type-definitions-format.service.spec.ts index dec82579..60f41a53 100644 --- a/projects/docs-tools/src/modules/type-definitions/type-definitions-format.service.spec.ts +++ b/projects/docs-tools/src/modules/type-definitions/type-definitions-format.service.spec.ts @@ -26,6 +26,7 @@ describe('Type definitions format service', () => { it('should generate method source code', () => { const def: SkyDocsClassMethodDefinition = { name: 'fooBar', + isPreview: false, type: { callSignature: { returnType: { @@ -44,6 +45,7 @@ describe('Type definitions format service', () => { const def: SkyDocsClassMethodDefinition = { name: 'fooBar', isStatic: true, + isPreview: false, type: { callSignature: { returnType: { @@ -61,6 +63,7 @@ describe('Type definitions format service', () => { it('should generate method source code with parameters', () => { const def: SkyDocsClassMethodDefinition = { name: 'fooBar', + isPreview: false, type: { callSignature: { parameters: [ @@ -99,6 +102,7 @@ describe('Type definitions format service', () => { const def: SkyDocsClassMethodDefinition = { name: 'getUserById', description: 'Gets a user from the database.', + isPreview: false, type: { callSignature: { returnType: { @@ -169,9 +173,11 @@ describe('Type definitions format service', () => { const def: SkyDocsInterfaceDefinition = { anchorId: 'foo-anchor-id', name: 'Foo', + hasPreviewFeatures: false, properties: [ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'typeParameter', @@ -180,6 +186,7 @@ describe('Type definitions format service', () => { }, { isOptional: true, + isPreview: false, name: 'user', type: { type: 'typeParameter', @@ -188,6 +195,7 @@ describe('Type definitions format service', () => { }, { isOptional: false, + isPreview: false, name: 'route', type: { type: 'reflection', @@ -195,6 +203,7 @@ describe('Type definitions format service', () => { properties: [ { isOptional: true, + isPreview: false, name: 'commands', type: { type: 'array', @@ -207,6 +216,7 @@ describe('Type definitions format service', () => { }, { isOptional: true, + isPreview: false, name: '__index', type: { indexSignature: { @@ -252,6 +262,7 @@ describe('Type definitions format service', () => { const def: SkyDocsTypeAliasDefinition = { anchorId: 'foo-anchor-id', name: 'FooTypeAlias', + isPreview: false, type: { type: 'union', unionTypes: [ @@ -306,6 +317,7 @@ describe('Type definitions format service', () => { const def: SkyDocsTypeAliasDefinition = { anchorId: 'foo-anchor-id', name: 'FooTypeAlias', + isPreview: false, type: { type: 'reflection', indexSignature: { @@ -334,6 +346,7 @@ describe('Type definitions format service', () => { const def: SkyDocsTypeAliasDefinition = { anchorId: 'foo-anchor-id', name: 'FooTypeAlias', + isPreview: false, type: { type: 'reflection', callSignature: { @@ -382,6 +395,7 @@ describe('Type definitions format service', () => { const def: SkyDocsClassPropertyDefinition = { name: 'foo', isOptional: true, + isPreview: false, type: { type: 'reference', name: 'FooUser', @@ -396,6 +410,7 @@ describe('Type definitions format service', () => { it('should generate HTML formatted property names without types', () => { const def: SkyDocsEnumerationMemberDefinition = { name: 'Foo', + isPreview: false, }; const formattedName = service.getFormattedPropertyName( @@ -407,6 +422,7 @@ describe('Type definitions format service', () => { it('should generate HTML formatted property names w/ call signature', () => { const def: SkyDocsClassPropertyDefinition = { isOptional: false, + isPreview: false, name: 'functions', type: { type: 'intrinsic', @@ -436,6 +452,7 @@ describe('Type definitions format service', () => { it('should generate HTML formatted property names w/ call signature', () => { const def: SkyDocsClassPropertyDefinition = { isOptional: false, + isPreview: false, name: 'functions', type: { type: 'array', @@ -465,6 +482,7 @@ describe('Type definitions format service', () => { it('should generate HTML formatted property names w/ union type parameters', () => { const def: SkyDocsClassPropertyDefinition = { isOptional: true, + isPreview: false, name: 'stream', type: { type: 'reference', @@ -507,6 +525,7 @@ describe('Type definitions format service', () => { it('should generate HTML formatted index signature property names', () => { const def: SkyDocsClassPropertyDefinition = { isOptional: true, + isPreview: false, name: '__index', type: { indexSignature: { @@ -533,6 +552,7 @@ describe('Type definitions format service', () => { const def: SkyDocsClassPropertyDefinition = { name: 'options', isOptional: false, + isPreview: false, type: { type: 'reference', name: 'Options', @@ -550,6 +570,7 @@ describe('Type definitions format service', () => { const def: SkyDocsClassPropertyDefinition = { name: 'click', isOptional: true, + isPreview: false, type: { type: 'reference', name: 'EventEmitter', @@ -576,6 +597,7 @@ describe('Type definitions format service', () => { const def: SkyDocsClassMethodDefinition = { name: 'getUserById', description: 'Gets a user from the database.', + isPreview: false, type: { callSignature: { returnType: { @@ -605,6 +627,7 @@ describe('Type definitions format service', () => { const def: SkyDocsClassMethodDefinition = { name: 'getUserById', deprecationWarning: '', + isPreview: false, type: { callSignature: { returnType: { diff --git a/projects/docs-tools/src/modules/type-definitions/type-definitions.module.ts b/projects/docs-tools/src/modules/type-definitions/type-definitions.module.ts index 3d57346b..3d8bddeb 100644 --- a/projects/docs-tools/src/modules/type-definitions/type-definitions.module.ts +++ b/projects/docs-tools/src/modules/type-definitions/type-definitions.module.ts @@ -9,7 +9,7 @@ import { SkyCodeModule, } from '@blackbaud/skyux-lib-code-block'; -import { SkyIconModule } from '@skyux/indicators'; +import { SkyIconModule, SkyStatusIndicatorModule } from '@skyux/indicators'; import { SkyDefinitionListModule } from '@skyux/layout'; @@ -38,6 +38,7 @@ import { SkyDocsClassDefinitionComponent } from './class-definition.component'; import { SkyDocsTypeAliasDefinitionComponent } from './type-alias-definition.component'; import { SkyDocsTypeAnchorLinksPipe } from './type-anchor-links.pipe'; +import { SkyDocsPreviewIndicatorComponent } from './preview-feature-indicator.component'; @NgModule({ imports: [ @@ -48,8 +49,10 @@ import { SkyDocsTypeAnchorLinksPipe } from './type-anchor-links.pipe'; SkyDefinitionListModule, SkyDocsHeadingAnchorModule, SkyDocsMarkdownModule, + SkyDocsPreviewIndicatorComponent, SkyDocsSafeHtmlModule, SkyIconModule, + SkyStatusIndicatorModule, ], declarations: [ SkyDocsCallSignatureDefinitionComponent, diff --git a/projects/docs-tools/src/modules/type-definitions/type-definitions.service.spec.ts b/projects/docs-tools/src/modules/type-definitions/type-definitions.service.spec.ts index 9e8e4a90..f9e4d9aa 100644 --- a/projects/docs-tools/src/modules/type-definitions/type-definitions.service.spec.ts +++ b/projects/docs-tools/src/modules/type-definitions/type-definitions.service.spec.ts @@ -14,7 +14,7 @@ import { TypeDocKind } from './typedoc-types'; describe('Type definitions service', function () { //#region helpers - function getService( + function getServiceAndAdapter( provider: SkyDocsTypeDefinitionsProvider = { anchorIds: {}, typeDefinitions: [ @@ -212,25 +212,34 @@ describe('Type definitions service', function () { }, ], } - ): SkyDocsTypeDefinitionsService { + ): { + service: SkyDocsTypeDefinitionsService; + adapter: SkyDocsTypeDocAdapterService; + } { const adapter = new MockTypeDocAdapterService(); - return new SkyDocsTypeDefinitionsService( - provider, - adapter as SkyDocsTypeDocAdapterService - ); + return { + service: new SkyDocsTypeDefinitionsService( + provider, + adapter as SkyDocsTypeDocAdapterService + ), + adapter: adapter, + }; } //#endregion it('should return type definitions from a specific source code path', () => { - const service = getService(); + const service = getServiceAndAdapter().service; const result = service.getTypeDefinitions( '/src/app/public/modules/_documentation-test/' ); for (const key in result) { - if (result.hasOwnProperty(key)) { - const lookup = key as keyof SkyDocsTypeDefinitions; + if (result.hasOwnProperty(key) && key !== 'hasPreviewFeatures') { + const lookup = key as keyof Omit< + SkyDocsTypeDefinitions, + 'hasPreviewFeatures' + >; expect(result[lookup].length) .withContext( 'The result is expected to have one item in each category.' @@ -241,14 +250,17 @@ describe('Type definitions service', function () { }); it('should return type definitions from a specific source code file', () => { - const service = getService(); + const service = getServiceAndAdapter().service; const result = service.getTypeDefinitions( '/src/app/public/modules/_documentation-test/foo-class.ts' ); for (const key in result) { - if (result.hasOwnProperty(key)) { - const lookup = key as keyof SkyDocsTypeDefinitions; + if (result.hasOwnProperty(key) && key !== 'hasPreviewFeatures') { + const lookup = key as keyof Omit< + SkyDocsTypeDefinitions, + 'hasPreviewFeatures' + >; if (lookup === 'classes') { expect(result[lookup].length) @@ -266,15 +278,18 @@ describe('Type definitions service', function () { }); it('should return type definitions from multiple source code paths', () => { - const service = getService(); + const service = getServiceAndAdapter().service; const result = service.getTypeDefinitions( '/src/app/public/modules/_documentation-test/', ['/src/app/public/modules/_documentation-test-2/'] ); for (const key in result) { - if (result.hasOwnProperty(key)) { - const lookup = key as keyof SkyDocsTypeDefinitions; + if (result.hasOwnProperty(key) && key !== 'hasPreviewFeatures') { + const lookup = key as keyof Omit< + SkyDocsTypeDefinitions, + 'hasPreviewFeatures' + >; expect(result[lookup].length) .withContext( 'The result is expected to have one item in each category.' @@ -286,7 +301,7 @@ describe('Type definitions service', function () { /* Purely a sanity check for null refs */ it('should throw an error if the `sourceCodePath` parameter is undefined.', () => { - const service = getService(); + const service = getServiceAndAdapter().service; expect(function () { service.getTypeDefinitions(undefined, [ '/src/app/public/modules/_documentation-test-2/', @@ -295,10 +310,10 @@ describe('Type definitions service', function () { }); it('should return empty type arrays if the path does not include types', () => { - const service = getService({ + const service = getServiceAndAdapter({ anchorIds: {}, typeDefinitions: undefined, - }); + }).service; const result = service.getTypeDefinitions('/src/app/public/modules/empty/'); expect(result).toEqual({ classes: [], @@ -309,14 +324,15 @@ describe('Type definitions service', function () { pipes: [], services: [], typeAliases: [], + hasPreviewFeatures: false, }); }); it('should return empty type arrays if the path to a file does not return a type', () => { - const service = getService({ + const service = getServiceAndAdapter({ anchorIds: {}, typeDefinitions: undefined, - }); + }).service; const result = service.getTypeDefinitions( '/src/app/public/modules/_documentation-test/empty-file.ts' ); @@ -329,15 +345,16 @@ describe('Type definitions service', function () { pipes: [], services: [], typeAliases: [], + hasPreviewFeatures: false, }); }); it('should warn if the provider does not include types', () => { const spy = spyOn(console, 'warn'); - const service = getService({ + const service = getServiceAndAdapter({ anchorIds: {}, typeDefinitions: [], - }); + }).service; service.getTypeDefinitions('/src/app/public/modules/empty/'); expect(spy).toHaveBeenCalledWith( 'Type definitions were not found for location: modules/empty/' @@ -345,7 +362,7 @@ describe('Type definitions service', function () { }); it('should throw an error if the source code path does not end in a slash or `.ts`', () => { - const service = getService(); + const service = getServiceAndAdapter().service; expect(function () { service.getTypeDefinitions('/src/app/public/modules/foobar'); }).toThrow( @@ -361,4 +378,297 @@ describe('Type definitions service', function () { ) ); }); + + it('should return type definitions which do not denote preview features when there are none', () => { + const service = getServiceAndAdapter().service; + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeFalse(); + }); + + it('should return type definitions which denote preview features when there is a class with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + name: 'FooClass', + kind: TypeDocKind.Class, + sources: [ + { + fileName: + 'src/app/public/modules/_documentation-test/foo-class.ts', + }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toClassDefinition').and.callFake( + (entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + name: entry.name, + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); + + it('should return type definitions which denote preview features when there is a service with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + decorators: [{ name: 'Injectable', type: {} }], + name: 'FooService', + sources: [ + { + fileName: + 'src/app/public/modules/_documentation-test/foo.service.ts', + }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toClassDefinition').and.callFake( + (entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + name: entry.name, + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); + + it('should return type definitions which denote preview features when there is a directive with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + decorators: [{ name: 'Directive', type: {} }], + name: 'FooDirective', + sources: [ + { + fileName: + 'src/app/public/modules/_documentation-test/foo.directive.ts', + }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toDirectiveDefinition').and.callFake( + (entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + name: entry.name, + selector: 'foo', + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); + + it('should return type definitions which denote preview features when there is a component with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + decorators: [{ name: 'Component', type: {} }], + name: 'FooComponent', + sources: [ + { + fileName: + 'src/app/public/modules/_documentation-test/foo.component.ts', + }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toDirectiveDefinition').and.callFake( + (entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + name: entry.name, + selector: 'foo', + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); + + it('should return type definitions which denote preview features when there is a enum with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + name: 'FooEnum', + kind: TypeDocKind.Enum, + sources: [ + { + fileName: + 'src/app/public/modules/_documentation-test/foo-enum.ts', + }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toEnumerationDefinition').and.callFake( + (entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + members: undefined, + name: entry.name, + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); + + it('should return type definitions which denote preview features when there is a interface with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + name: 'Foo', + kind: TypeDocKind.Interface, + sources: [ + { fileName: 'src/app/public/modules/_documentation-test/foo.ts' }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toInterfaceDefinition').and.callFake( + (entry) => { + return { + hasPreviewFeatures: true, + anchorId: entry.anchorId, + name: entry.name, + properties: [], + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); + + it('should return type definitions which denote preview features when there is a pipe with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + decorators: [{ name: 'Pipe', type: {} }], + name: 'FooPipe', + sources: [ + { + fileName: + 'src/app/public/modules/_documentation-test/foo.pipe.ts', + }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toPipeDefinition').and.callFake( + (entry) => { + return { + anchorId: entry.anchorId, + name: entry.name, + transformMethod: { + name: 'transform', + isPreview: true, + type: { + callSignature: { + returnType: { + name: 'stringj', + }, + }, + }, + }, + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); + + it('should return type definitions which denote preview features when there is a type alias with a preview feature', () => { + const serviceAndAdapter = getServiceAndAdapter({ + anchorIds: {}, + typeDefinitions: [ + { + anchorId: '', + name: 'TypeAlias', + kind: TypeDocKind.TypeAlias, + sources: [ + { + fileName: + 'src/app/public/modules/_documentation-test/foo-alias.ts', + }, + ], + }, + ], + }); + const service = serviceAndAdapter.service; + spyOn(serviceAndAdapter.adapter, 'toTypeAliasDefinition').and.callFake( + (entry) => { + return { + anchorId: entry.anchorId, + name: entry.name, + isPreview: true, + type: {}, + }; + } + ); + const result = service.getTypeDefinitions( + '/src/app/public/modules/_documentation-test/' + ); + + expect(result.hasPreviewFeatures).toBeTrue(); + }); }); diff --git a/projects/docs-tools/src/modules/type-definitions/type-definitions.service.ts b/projects/docs-tools/src/modules/type-definitions/type-definitions.service.ts index db52a68f..a548db8c 100644 --- a/projects/docs-tools/src/modules/type-definitions/type-definitions.service.ts +++ b/projects/docs-tools/src/modules/type-definitions/type-definitions.service.ts @@ -55,6 +55,7 @@ export class SkyDocsTypeDefinitionsService { pipes: [], services: [], typeAliases: [], + hasPreviewFeatures: false, }; if (!allDefinitions) { @@ -83,38 +84,50 @@ export class SkyDocsTypeDefinitionsService { switch (decorator) { case 'Component': - types.components.push(this.adapter.toDirectiveDefinition(item)); + const componentType = this.adapter.toDirectiveDefinition(item); + types.components.push(componentType); + types.hasPreviewFeatures ||= componentType.hasPreviewFeatures; break; case 'Directive': - types.directives.push(this.adapter.toDirectiveDefinition(item)); + const directiveType = this.adapter.toDirectiveDefinition(item); + types.directives.push(directiveType); + types.hasPreviewFeatures ||= directiveType.hasPreviewFeatures; break; case 'Injectable': - types.services.push(this.adapter.toClassDefinition(item)); + const serviceType = this.adapter.toClassDefinition(item); + types.services.push(serviceType); + types.hasPreviewFeatures ||= serviceType.hasPreviewFeatures; break; case 'NgModule': // Don't document modules. break; case 'Pipe': - types.pipes.push(this.adapter.toPipeDefinition(item)); + const pipeType = this.adapter.toPipeDefinition(item); + types.pipes.push(pipeType); + types.hasPreviewFeatures ||= pipeType.transformMethod.isPreview; break; default: /*tslint:disable-next-line:switch-default*/ switch (kind) { case TypeDocKind.Class: - types.classes.push(this.adapter.toClassDefinition(item)); + const classType = this.adapter.toClassDefinition(item); + types.classes.push(classType); + types.hasPreviewFeatures ||= classType.hasPreviewFeatures; break; case TypeDocKind.Interface: - types.interfaces.push(this.adapter.toInterfaceDefinition(item)); + const interfaceType = this.adapter.toInterfaceDefinition(item); + types.interfaces.push(interfaceType); + types.hasPreviewFeatures ||= interfaceType.hasPreviewFeatures; break; case TypeDocKind.Enum: - types.enumerations.push( - this.adapter.toEnumerationDefinition(item) - ); + const enumType = this.adapter.toEnumerationDefinition(item); + types.enumerations.push(enumType); + types.hasPreviewFeatures ||= enumType.hasPreviewFeatures; break; case TypeDocKind.TypeAlias: - types.typeAliases.push( - this.adapter.toTypeAliasDefinition(item) - ); + const typeAliasType = this.adapter.toTypeAliasDefinition(item); + types.typeAliases.push(typeAliasType); + types.hasPreviewFeatures ||= !!typeAliasType.isPreview; break; } } diff --git a/projects/docs-tools/src/modules/type-definitions/type-definitions.ts b/projects/docs-tools/src/modules/type-definitions/type-definitions.ts index 0af6e3ca..17ce20b7 100644 --- a/projects/docs-tools/src/modules/type-definitions/type-definitions.ts +++ b/projects/docs-tools/src/modules/type-definitions/type-definitions.ts @@ -22,6 +22,8 @@ export interface SkyDocsTypeDefinitions { enumerations: SkyDocsEnumerationDefinition[]; + hasPreviewFeatures: boolean; + interfaces: SkyDocsInterfaceDefinition[]; pipes: SkyDocsPipeDefinition[]; diff --git a/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.spec.ts b/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.spec.ts index be156170..9aa1cd38 100644 --- a/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.spec.ts +++ b/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.spec.ts @@ -25,6 +25,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: 'foo-anchor-id', name: 'FooClass', + hasPreviewFeatures: false, }); }); @@ -54,6 +55,7 @@ describe('TypeDoc adapter', () => { { name: 'fooA', isOptional: true, + isPreview: false, type: { name: 'string', type: 'intrinsic', @@ -62,6 +64,7 @@ describe('TypeDoc adapter', () => { { name: 'fooB', isOptional: true, + isPreview: false, type: { name: 'number', type: 'intrinsic', @@ -104,6 +107,7 @@ describe('TypeDoc adapter', () => { { name: 'fooZ', isOptional: false, + isPreview: false, type: { name: 'string', type: 'intrinsic', @@ -112,6 +116,64 @@ describe('TypeDoc adapter', () => { { name: 'fooA', isOptional: true, + isPreview: false, + type: { + name: 'number', + type: 'intrinsic', + }, + }, + ]); + }); + + it('should properly denote preview features when a property is in preview', () => { + entry.children = [ + { + name: 'fooA', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'number', + }, + }, + { + name: 'fooZ', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'string', + }, + comment: { + blockTags: [ + { + tag: '@preview', + content: [], + }, + { + tag: '@required', + content: [], + }, + ], + }, + }, + ]; + + const def = adapter.toClassDefinition(entry); + + expect(def.hasPreviewFeatures).toBeTrue(); + expect(def.properties).toEqual([ + { + name: 'fooZ', + isOptional: false, + isPreview: true, + type: { + name: 'string', + type: 'intrinsic', + }, + }, + { + name: 'fooA', + isOptional: true, + isPreview: false, type: { name: 'number', type: 'intrinsic', @@ -184,6 +246,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -254,6 +317,149 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: false, + isPreview: false, + name: 'foo', + type: { + type: 'intrinsic', + name: 'number', + }, + description: 'The foo of the FooClass.', + defaultValue: '10', + deprecationWarning: 'This property is deprecated.', + }, + ]); + }); + + it('should handle property accessors', () => { + entry.children = [ + { + name: 'foo', + kind: TypeDocKind.Accessor, + comment: {}, + getSignature: { + name: '__get', + comment: { + summary: [{ kind: 'text', text: 'The foo of the FooClass.' }], + blockTags: [ + { + tag: '@default', + content: [{ kind: 'code', text: '```ts\n10\n```' }], + }, + { + tag: '@preview', + content: [], + }, + ], + }, + type: { + type: 'intrinsic', + name: 'number', + }, + }, + setSignature: { + name: '__set', + comment: {}, + parameters: [ + { + name: 'value', + kind: TypeDocKind.Parameter, + type: { + type: 'intrinsic', + name: 'number', + }, + }, + ], + type: { + type: 'intrinsic', + name: 'void', + }, + }, + }, + ]; + + const def = adapter.toClassDefinition(entry); + + expect(def.hasPreviewFeatures).toBeTrue(); + expect(def.properties).toEqual([ + { + isOptional: true, + isPreview: true, + name: 'foo', + type: { + type: 'intrinsic', + name: 'number', + }, + description: 'The foo of the FooClass.', + defaultValue: '10', + }, + ]); + }); + + it('should handle preview features when the comment is in the `setSignature`', () => { + entry.children = [ + { + name: 'foo', + kind: TypeDocKind.Accessor, + getSignature: { + name: '__get', + comment: { + summary: [{ kind: 'text', text: 'The foo of the FooClass.' }], + }, + type: { + type: 'intrinsic', + name: 'number', + }, + }, + setSignature: { + name: '__set', + comment: { + summary: [{ kind: 'text', text: 'The foo of the FooClass.' }], + blockTags: [ + { + tag: '@default', + content: [{ kind: 'code', text: '```ts\n10\n```' }], + }, + { + tag: '@preview', + content: [], + }, + { + tag: '@required', + content: [], + }, + { + tag: '@deprecated', + content: [ + { kind: 'text', text: 'This property is deprecated.\n' }, + ], + }, + ], + }, + parameters: [ + { + name: 'value', + kind: TypeDocKind.Parameter, + type: { + type: 'intrinsic', + name: 'number', + }, + }, + ], + type: { + type: 'intrinsic', + name: 'void', + }, + }, + }, + ]; + + const def = adapter.toClassDefinition(entry); + + expect(def.hasPreviewFeatures).toBeTrue(); + expect(def.properties).toEqual([ + { + isOptional: false, + isPreview: true, name: 'foo', type: { type: 'intrinsic', @@ -288,6 +494,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -329,6 +536,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -376,6 +584,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -424,6 +633,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -488,6 +698,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -561,6 +772,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'foo', type: { type: 'intrinsic', @@ -627,6 +839,7 @@ describe('TypeDoc adapter', () => { name: 'fooA', description: 'fooA description', isOptional: true, + isPreview: false, type: { name: 'string', type: 'intrinsic', @@ -635,6 +848,7 @@ describe('TypeDoc adapter', () => { { name: 'fooB', isOptional: true, + isPreview: false, type: { name: 'number', type: 'intrinsic', @@ -679,6 +893,7 @@ describe('TypeDoc adapter', () => { { name: 'fooA', isOptional: true, + isPreview: false, type: { name: 'string', type: 'intrinsic', @@ -687,6 +902,7 @@ describe('TypeDoc adapter', () => { { name: 'fooB', isOptional: true, + isPreview: false, type: { name: 'number', type: 'intrinsic', @@ -726,6 +942,7 @@ describe('TypeDoc adapter', () => { { name: 'fooUnion', isOptional: true, + isPreview: false, type: { type: 'union', unionTypes: [ @@ -860,6 +1077,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: false, + isPreview: false, codeExample: '[searchFunction]="mySearchFunction"', codeExampleLanguage: 'markup', decorator: { @@ -950,6 +1168,7 @@ describe('TypeDoc adapter', () => { expect(def.properties as any).toEqual([ { isOptional: true, + isPreview: false, name: 'messageStream', type: { type: 'reference', @@ -1005,6 +1224,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: 'anchorIds', type: { type: 'reflection', @@ -1084,6 +1304,7 @@ describe('TypeDoc adapter', () => { { name: 'getA', isStatic: true, + isPreview: false, type: { callSignature: { returnType: { @@ -1098,6 +1319,7 @@ describe('TypeDoc adapter', () => { { name: 'getB', isStatic: false, + isPreview: false, type: { callSignature: { returnType: { @@ -1112,6 +1334,7 @@ describe('TypeDoc adapter', () => { { name: 'getC', isStatic: false, + isPreview: false, type: { callSignature: { returnType: { @@ -1126,24 +1349,60 @@ describe('TypeDoc adapter', () => { ]); }); - it('should handle type parameters on methods', () => { + it('should properly denote preview features when a method is in preview', () => { entry.children = [ { - name: 'getUser', + name: 'getB', kind: TypeDocKind.Method, signatures: [ { - name: 'getUser', kind: TypeDocKind.CallSignature, - typeParameters: [ - { - name: 'T', - kind: TypeDocKind.TypeParameter, - }, - ], + name: 'getB', type: { - type: 'typeParameter', - name: 'T', + type: 'intrinsic', + name: 'string', + }, + comment: { + blockTags: [ + { + tag: '@preview', + content: [], + }, + ], + }, + }, + ], + }, + { + name: 'getA', + kind: TypeDocKind.Method, + flags: { + isStatic: true, + }, + signatures: [ + { + kind: TypeDocKind.CallSignature, + name: 'getA', + type: { + type: 'intrinsic', + name: 'void', + }, + }, + ], + }, + { + name: 'getC', + kind: TypeDocKind.Method, + flags: { + isStatic: false, + }, + signatures: [ + { + kind: TypeDocKind.CallSignature, + name: 'getC', + type: { + type: 'intrinsic', + name: 'void', }, }, ], @@ -1152,24 +1411,101 @@ describe('TypeDoc adapter', () => { const def = adapter.toClassDefinition(entry); + expect(def.hasPreviewFeatures).toBeTrue(); expect(def.methods).toEqual([ { - name: 'getUser', - isStatic: false, + name: 'getA', + isStatic: true, + isPreview: false, type: { callSignature: { returnType: { - name: 'T', - type: 'typeParameter', + name: 'void', + type: 'intrinsic', }, }, - name: 'getUser', + name: 'getA', }, - typeParameters: [ - { - name: 'T', - }, - ], + parentName: 'FooClass', + }, + { + name: 'getB', + isStatic: false, + isPreview: true, + type: { + callSignature: { + returnType: { + name: 'string', + type: 'intrinsic', + }, + }, + name: 'getB', + }, + parentName: 'FooClass', + }, + { + name: 'getC', + isStatic: false, + isPreview: false, + type: { + callSignature: { + returnType: { + name: 'void', + type: 'intrinsic', + }, + }, + name: 'getC', + }, + parentName: 'FooClass', + }, + ]); + }); + + it('should handle type parameters on methods', () => { + entry.children = [ + { + name: 'getUser', + kind: TypeDocKind.Method, + signatures: [ + { + name: 'getUser', + kind: TypeDocKind.CallSignature, + typeParameters: [ + { + name: 'T', + kind: TypeDocKind.TypeParameter, + }, + ], + type: { + type: 'typeParameter', + name: 'T', + }, + }, + ], + }, + ]; + + const def = adapter.toClassDefinition(entry); + + expect(def.methods).toEqual([ + { + name: 'getUser', + isStatic: false, + isPreview: false, + type: { + callSignature: { + returnType: { + name: 'T', + type: 'typeParameter', + }, + }, + name: 'getUser', + }, + typeParameters: [ + { + name: 'T', + }, + ], parentName: 'FooClass', }, ]); @@ -1261,6 +1597,7 @@ describe('TypeDoc adapter', () => { name: 'getUserById', description: 'Gets a user from the database.', isStatic: false, + isPreview: false, type: { callSignature: { returnType: { @@ -1399,6 +1736,7 @@ describe('TypeDoc adapter', () => { codeExampleLanguage: 'markup', name: 'defaultProperty', isOptional: true, + isPreview: false, type: { name: 'void', type: 'intrinsic', @@ -1409,6 +1747,7 @@ describe('TypeDoc adapter', () => { codeExampleLanguage: 'markup', name: 'markupProperty', isOptional: true, + isPreview: false, type: { name: 'void', type: 'intrinsic', @@ -1419,6 +1758,7 @@ describe('TypeDoc adapter', () => { codeExampleLanguage: 'typescript', name: 'typescriptProperty', isOptional: true, + isPreview: false, type: { name: 'void', type: 'intrinsic', @@ -1456,6 +1796,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: 'foo-anchor-id', name: 'FooDirective', + hasPreviewFeatures: false, selector: '[foo]', }); }); @@ -1469,6 +1810,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: 'foo-anchor-id', name: 'FooDirective', + hasPreviewFeatures: false, selector: 'input[fooComplex], textarea[fooComplex], [required][fooComplex]', }); @@ -1600,6 +1942,7 @@ describe('TypeDoc adapter', () => { { name: 'fooA', isOptional: true, + isPreview: false, type: { name: 'string', type: 'intrinsic', @@ -1611,6 +1954,7 @@ describe('TypeDoc adapter', () => { { name: 'fooC', isOptional: true, + isPreview: false, type: { name: 'string', type: 'intrinsic', @@ -1625,6 +1969,7 @@ describe('TypeDoc adapter', () => { { name: 'fooD', isOptional: true, + isPreview: false, type: { type: 'reference', name: 'EventEmitter', @@ -1642,6 +1987,7 @@ describe('TypeDoc adapter', () => { }, { isOptional: true, + isPreview: false, name: 'stream', type: { type: 'reference', @@ -1677,15 +2023,31 @@ describe('TypeDoc adapter', () => { ]); }); - it("should use an Input's binding property name", () => { + it('should denote preview features when an input is in preview', () => { entry.children = [ { - name: 'originalPropertyName', + name: 'fooB', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'number', + }, + }, + { + name: 'fooC', kind: TypeDocKind.Property, type: { type: 'intrinsic', name: 'string', }, + comment: { + blockTags: [ + { + tag: '@preview', + content: [], + }, + ], + }, decorators: [ { name: 'Input', @@ -1693,20 +2055,110 @@ describe('TypeDoc adapter', () => { type: 'reference', name: 'Input', }, - arguments: { - bindingPropertyName: 'boundName', + }, + ], + }, + { + name: 'fooA', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'string', + }, + decorators: [ + { + name: 'Input', + type: { + type: 'reference', + name: 'Input', + }, + }, + ], + }, + { + name: 'fooD', + kind: TypeDocKind.Property, + type: { + type: 'reference', + typeArguments: [ + { + type: 'array', + elementType: { + type: 'reference', + name: 'FooUser', + }, + }, + ], + name: 'EventEmitter', + }, + defaultValue: 'new EventEmitter()', + decorators: [ + { + name: 'Output', + type: { + type: 'reference', + name: 'Output', + }, + }, + ], + }, + { + name: 'stream', + kind: TypeDocKind.Property, + decorators: [ + { + name: 'Output', + type: { + type: 'reference', + name: 'Output', }, + arguments: {}, }, ], + type: { + type: 'reference', + typeArguments: [ + { + type: 'union', + types: [ + { + type: 'array', + elementType: { + type: 'intrinsic', + name: 'string', + }, + }, + { + type: 'reference', + typeArguments: [ + { + type: 'array', + elementType: { + type: 'intrinsic', + name: 'string', + }, + }, + ], + name: 'Observable', + }, + ], + }, + ], + name: 'EventEmitter', + }, + defaultValue: + 'new EventEmitter | Observable>>()', }, ]; const def = adapter.toDirectiveDefinition(entry); + expect(def.hasPreviewFeatures).toBeTrue(); expect(def.inputProperties).toEqual([ { - name: 'boundName', + name: 'fooA', isOptional: true, + isPreview: false, type: { name: 'string', type: 'intrinsic', @@ -1715,84 +2167,638 @@ describe('TypeDoc adapter', () => { name: 'Input', }, }, - ]); - }); - }); - - describe('Enumeration definitions', () => { - let entry: TypeDocEntry; - - beforeEach(() => { - entry = { - anchorId: 'foo-anchor-id', - name: 'FooEnum', - }; - }); - - it('should convert with defaults', () => { - const def = adapter.toEnumerationDefinition(entry); - - expect(def).toEqual({ - anchorId: 'foo-anchor-id', - name: 'FooEnum', - members: [], - }); - }); - - it('should convert enumeration members', () => { - entry.children = [ { - name: 'A', + name: 'fooC', + isOptional: true, + isPreview: true, + type: { + name: 'string', + type: 'intrinsic', + }, + decorator: { + name: 'Input', + }, }, + ]); + + expect(def.eventProperties).toEqual([ { - name: 'B', + name: 'fooD', + isOptional: true, + isPreview: false, + type: { + type: 'reference', + name: 'EventEmitter', + typeArguments: [ + { + type: 'array', + name: 'FooUser', + }, + ], + }, + decorator: { + name: 'Output', + }, + defaultValue: 'new EventEmitter()', }, { - name: 'C', - }, - ]; - - const def = adapter.toEnumerationDefinition(entry); - - expect(def).toEqual({ - anchorId: 'foo-anchor-id', - name: 'FooEnum', - members: [ - { + isOptional: true, + isPreview: false, + name: 'stream', + type: { + type: 'reference', + name: 'EventEmitter', + typeArguments: [ + { + type: 'union', + unionTypes: [ + { + type: 'array', + name: 'string', + }, + { + type: 'reference', + name: 'Observable', + typeArguments: [ + { + type: 'array', + name: 'string', + }, + ], + }, + ], + }, + ], + }, + decorator: { + name: 'Output', + }, + defaultValue: + 'new EventEmitter | Observable>>()', + }, + ]); + }); + + it('should denote preview features when an input is in preview', () => { + entry.children = [ + { + name: 'fooB', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'number', + }, + }, + { + name: 'fooC', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'string', + }, + decorators: [ + { + name: 'Input', + type: { + type: 'reference', + name: 'Input', + }, + }, + ], + }, + { + name: 'fooA', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'string', + }, + decorators: [ + { + name: 'Input', + type: { + type: 'reference', + name: 'Input', + }, + }, + ], + }, + { + name: 'fooD', + kind: TypeDocKind.Property, + type: { + type: 'reference', + typeArguments: [ + { + type: 'array', + elementType: { + type: 'reference', + name: 'FooUser', + }, + }, + ], + name: 'EventEmitter', + }, + defaultValue: 'new EventEmitter()', + decorators: [ + { + name: 'Output', + type: { + type: 'reference', + name: 'Output', + }, + }, + ], + }, + { + name: 'stream', + kind: TypeDocKind.Property, + comment: { + blockTags: [ + { + tag: '@preview', + content: [], + }, + ], + }, + decorators: [ + { + name: 'Output', + type: { + type: 'reference', + name: 'Output', + }, + arguments: {}, + }, + ], + type: { + type: 'reference', + typeArguments: [ + { + type: 'union', + types: [ + { + type: 'array', + elementType: { + type: 'intrinsic', + name: 'string', + }, + }, + { + type: 'reference', + typeArguments: [ + { + type: 'array', + elementType: { + type: 'intrinsic', + name: 'string', + }, + }, + ], + name: 'Observable', + }, + ], + }, + ], + name: 'EventEmitter', + }, + defaultValue: + 'new EventEmitter | Observable>>()', + }, + ]; + + const def = adapter.toDirectiveDefinition(entry); + + expect(def.hasPreviewFeatures).toBeTrue(); + expect(def.inputProperties).toEqual([ + { + name: 'fooA', + isOptional: true, + isPreview: false, + type: { + name: 'string', + type: 'intrinsic', + }, + decorator: { + name: 'Input', + }, + }, + { + name: 'fooC', + isOptional: true, + isPreview: false, + type: { + name: 'string', + type: 'intrinsic', + }, + decorator: { + name: 'Input', + }, + }, + ]); + + expect(def.eventProperties).toEqual([ + { + name: 'fooD', + isOptional: true, + isPreview: false, + type: { + type: 'reference', + name: 'EventEmitter', + typeArguments: [ + { + type: 'array', + name: 'FooUser', + }, + ], + }, + decorator: { + name: 'Output', + }, + defaultValue: 'new EventEmitter()', + }, + { + isOptional: true, + isPreview: true, + name: 'stream', + type: { + type: 'reference', + name: 'EventEmitter', + typeArguments: [ + { + type: 'union', + unionTypes: [ + { + type: 'array', + name: 'string', + }, + { + type: 'reference', + name: 'Observable', + typeArguments: [ + { + type: 'array', + name: 'string', + }, + ], + }, + ], + }, + ], + }, + decorator: { + name: 'Output', + }, + defaultValue: + 'new EventEmitter | Observable>>()', + }, + ]); + }); + + it("should use an Input's binding property name", () => { + entry.children = [ + { + name: 'originalPropertyName', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'string', + }, + decorators: [ + { + name: 'Input', + type: { + type: 'reference', + name: 'Input', + }, + arguments: { + bindingPropertyName: 'boundName', + }, + }, + ], + }, + ]; + + const def = adapter.toDirectiveDefinition(entry); + + expect(def.inputProperties).toEqual([ + { + name: 'boundName', + isOptional: true, + isPreview: false, + type: { + name: 'string', + type: 'intrinsic', + }, + decorator: { + name: 'Input', + }, + }, + ]); + }); + }); + + describe('Enumeration definitions', () => { + let entry: TypeDocEntry; + + beforeEach(() => { + entry = { + anchorId: 'foo-anchor-id', + name: 'FooEnum', + }; + }); + + it('should convert with defaults', () => { + const def = adapter.toEnumerationDefinition(entry); + + expect(def).toEqual({ + anchorId: 'foo-anchor-id', + name: 'FooEnum', + hasPreviewFeatures: false, + members: [], + }); + }); + + it('should convert enumeration members', () => { + entry.children = [ + { + name: 'A', + }, + { + name: 'B', + }, + { + name: 'C', + }, + ]; + + const def = adapter.toEnumerationDefinition(entry); + + expect(def).toEqual({ + anchorId: 'foo-anchor-id', + name: 'FooEnum', + hasPreviewFeatures: false, + members: [ + { + name: 'A', + isPreview: false, + }, + { + name: 'B', + isPreview: false, + }, + { + name: 'C', + isPreview: false, + }, + ], + }); + }); + + it('should denote preview features when a enum member is in preview', () => { + entry.children = [ + { + name: 'A', + }, + { + name: 'B', + }, + { + name: 'C', + comment: { + blockTags: [ + { + tag: '@preview', + content: [], + }, + ], + }, + }, + ]; + + const def = adapter.toEnumerationDefinition(entry); + + expect(def).toEqual({ + anchorId: 'foo-anchor-id', + name: 'FooEnum', + hasPreviewFeatures: true, + members: [ + { name: 'A', + isPreview: false, }, { name: 'B', + isPreview: false, }, { name: 'C', + isPreview: true, + }, + ], + }); + }); + }); + + describe('Interface definitions', () => { + let entry: TypeDocEntry; + + beforeEach(() => { + entry = { + anchorId: 'foo-anchor-id', + name: 'FooInterface', + }; + }); + + it('should convert with defaults', () => { + const def = adapter.toInterfaceDefinition(entry); + + expect(def).toEqual({ + anchorId: 'foo-anchor-id', + name: 'FooInterface', + hasPreviewFeatures: false, + properties: [], + }); + }); + + it('should convert properties', () => { + entry.children = [ + { + name: 'fooB', + kind: TypeDocKind.Property, + flags: { + isOptional: true, + }, + type: { + type: 'reference', + name: 'FooUser', + }, + }, + { + name: 'fooA', + kind: TypeDocKind.Property, + flags: { + isOptional: true, + }, + type: { + type: 'intrinsic', + name: 'string', + }, + }, + { + name: 'fooZ', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'string', + }, + }, + { + name: 'fooC', + kind: TypeDocKind.Property, + type: { + type: 'intrinsic', + name: 'string', + }, + comment: { + blockTags: [ + { + tag: '@required', + content: [], + }, + ], + }, + }, + { + name: 'bar', + kind: TypeDocKind.Method, + signatures: [ + { + name: 'bar', + comment: { + summary: [ + { + kind: 'text', + text: 'method ', + }, + { + kind: 'text', + text: 'description', + }, + ], + }, + type: { + type: 'intrinsic', + name: 'void', + }, + }, + ], + }, + { + name: 'moo', + kind: TypeDocKind.Property, + type: { + type: 'reflection', + declaration: { + signatures: [ + { + name: 'moo', + comment: { + summary: [ + { + kind: 'text', + text: 'method ', + }, + { + kind: 'text', + text: 'description', + }, + ], + }, + type: { + type: 'intrinsic', + name: 'void', + }, + }, + ], + }, }, - ], - }); - }); - }); - - describe('Interface definitions', () => { - let entry: TypeDocEntry; - - beforeEach(() => { - entry = { - anchorId: 'foo-anchor-id', - name: 'FooInterface', - }; - }); + }, + ]; - it('should convert with defaults', () => { const def = adapter.toInterfaceDefinition(entry); - expect(def).toEqual({ - anchorId: 'foo-anchor-id', - name: 'FooInterface', - properties: [], - }); + expect(def.properties).toEqual([ + { + isOptional: false, + isPreview: false, + name: 'bar', + description: 'method description', + type: { + callSignature: { + returnType: { + type: 'intrinsic', + name: 'void', + }, + }, + name: 'bar', + }, + }, + { + isOptional: false, + isPreview: false, + name: 'fooC', + type: { + type: 'intrinsic', + name: 'string', + }, + }, + { + isOptional: false, + isPreview: false, + name: 'fooZ', + type: { + type: 'intrinsic', + name: 'string', + }, + }, + { + isOptional: false, + isPreview: false, + name: 'moo', + description: 'method description', + type: { + type: 'reflection', + callSignature: { + returnType: { + type: 'intrinsic', + name: 'void', + }, + }, + }, + }, + { + isOptional: true, + isPreview: false, + name: 'fooA', + type: { + type: 'intrinsic', + name: 'string', + }, + }, + { + isOptional: true, + isPreview: false, + name: 'fooB', + type: { + type: 'reference', + name: 'FooUser', + }, + }, + ]); }); - it('should convert properties', () => { + it('should denote preview features when a property is in preview', () => { entry.children = [ { name: 'fooB', @@ -1811,6 +2817,14 @@ describe('TypeDoc adapter', () => { flags: { isOptional: true, }, + comment: { + blockTags: [ + { + tag: '@preview', + content: [], + }, + ], + }, type: { type: 'intrinsic', name: 'string', @@ -1899,9 +2913,11 @@ describe('TypeDoc adapter', () => { const def = adapter.toInterfaceDefinition(entry); + expect(def.hasPreviewFeatures).toBeTrue(); expect(def.properties).toEqual([ { isOptional: false, + isPreview: false, name: 'bar', description: 'method description', type: { @@ -1916,6 +2932,7 @@ describe('TypeDoc adapter', () => { }, { isOptional: false, + isPreview: false, name: 'fooC', type: { type: 'intrinsic', @@ -1924,6 +2941,7 @@ describe('TypeDoc adapter', () => { }, { isOptional: false, + isPreview: false, name: 'fooZ', type: { type: 'intrinsic', @@ -1932,6 +2950,7 @@ describe('TypeDoc adapter', () => { }, { isOptional: false, + isPreview: false, name: 'moo', description: 'method description', type: { @@ -1946,6 +2965,7 @@ describe('TypeDoc adapter', () => { }, { isOptional: true, + isPreview: true, name: 'fooA', type: { type: 'intrinsic', @@ -1954,6 +2974,7 @@ describe('TypeDoc adapter', () => { }, { isOptional: true, + isPreview: false, name: 'fooB', type: { type: 'reference', @@ -2019,6 +3040,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: false, + isPreview: false, name: 'foo', type: { type: 'typeParameter', @@ -2027,6 +3049,7 @@ describe('TypeDoc adapter', () => { }, { isOptional: false, + isPreview: false, name: 'user', type: { type: 'typeParameter', @@ -2061,6 +3084,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: '__index', description: 'All other properties for an item.', type: { @@ -2115,6 +3139,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: true, + isPreview: false, name: '__index', description: 'Test description.', type: { @@ -2169,6 +3194,7 @@ describe('TypeDoc adapter', () => { expect(def.properties).toEqual([ { isOptional: false, + isPreview: false, name: 'route', type: { type: 'reflection', @@ -2176,6 +3202,7 @@ describe('TypeDoc adapter', () => { properties: [ { isOptional: true, + isPreview: false, name: 'commands', type: { type: 'array', @@ -2247,6 +3274,49 @@ describe('TypeDoc adapter', () => { transformMethod: { name: 'transform', isStatic: false, + isPreview: false, + type: { + name: 'transform', + callSignature: { + parameters: [ + { + isOptional: false, + name: 'value', + type: { + type: 'reference', + name: 'Date', + }, + }, + ], + returnType: { + type: 'intrinsic', + name: 'string', + }, + }, + }, + parentName: 'FooPipe', + }, + }); + }); + + it('should denote preview features when the transform method is in preview', () => { + entry.children[0].signatures[0].comment = { + blockTags: [ + { + tag: '@preview', + content: [], + }, + ], + }; + const def = adapter.toPipeDefinition(entry); + + expect(def).toEqual({ + anchorId: 'foo-anchor-id', + name: 'FooPipe', + transformMethod: { + name: 'transform', + isStatic: false, + isPreview: true, type: { name: 'transform', callSignature: { @@ -2342,6 +3412,134 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: 'foo-anchor-id', name: 'FooTypeAlias', + isPreview: false, + type: { + type: 'union', + unionTypes: [ + { + type: 'intrinsic', + name: 'string', + }, + { + type: 'reference', + name: 'FooDate', + }, + { + type: 'intrinsic', + name: 'number', + }, + { + type: 'intrinsic', + name: 'false', + }, + { + type: 'unknown', + name: '1', + }, + { + type: 'literal', + name: 'left', + }, + { + type: 'typeParameter', + name: 'T', + }, + { + type: 'reflection', + callSignature: { + returnType: { + type: 'intrinsic', + name: 'void', + }, + }, + }, + { + type: 'typeOperator', + name: 'keyof FooUser', + }, + ], + }, + }); + }); + + it('should denote preview features if the type alias is in preview', () => { + const entry: TypeDocEntry = { + anchorId: 'foo-anchor-id', + name: 'FooTypeAlias', + comment: { + blockTags: [ + { + tag: '@preview', + content: [], + }, + ], + }, + type: { + type: 'union', + types: [ + { + type: 'intrinsic', + name: 'string', + }, + { + type: 'reference', + name: 'FooDate', + }, + { + type: 'intrinsic', + name: 'number', + }, + { + type: 'intrinsic', + name: 'false', + }, + { + type: 'unknown', + name: '1', + }, + { + type: 'literal', + name: 'left', + }, + { + type: 'typeParameter', + name: 'T', + constraint: { + name: 'FooUser', + }, + }, + { + type: 'reflection', + declaration: { + signatures: [ + { + name: '__call', + kind: TypeDocKind.CallSignature, + type: { + type: 'intrinsic', + name: 'void', + }, + }, + ], + }, + }, + { + type: 'typeOperator', + operator: 'keyof', + target: { + name: 'FooUser', + }, + }, + ], + }, + }; + + const def = adapter.toTypeAliasDefinition(entry); + + expect(def).toEqual({ + anchorId: 'foo-anchor-id', + name: 'FooTypeAlias', + isPreview: true, type: { type: 'union', unionTypes: [ @@ -2425,6 +3623,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: 'foo-anchor-id', name: 'FooTypeAlias', + isPreview: false, type: { type: 'reflection', indexSignature: { @@ -2512,6 +3711,7 @@ describe('TypeDoc adapter', () => { anchorId: 'foo-anchor-id', name: 'FooTypeAlias', description: 'test description', + isPreview: false, type: { type: 'reflection', callSignature: { @@ -2621,6 +3821,7 @@ describe('TypeDoc adapter', () => { anchorId: 'foo-anchor-id', name: 'FooTypeAlias', description: 'test description', + isPreview: false, type: { type: 'array', callSignature: { @@ -2688,6 +3889,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: 'foo-anchor-id', name: 'FooTypeAlias', + isPreview: false, type: { type: 'union', unionTypes: [ @@ -2721,6 +3923,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: 'class-skyfoobar', name: 'SkyFoobar', + hasPreviewFeatures: false, }); }); @@ -2737,6 +3940,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: '', name: 'SkyFoobar', + hasPreviewFeatures: false, }); }); @@ -2750,6 +3954,7 @@ describe('TypeDoc adapter', () => { expect(def).toEqual({ anchorId: '', name: 'SkyFoobar', + hasPreviewFeatures: false, }); }); }); diff --git a/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.ts b/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.ts index 573d952d..5ce9d848 100644 --- a/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.ts +++ b/projects/docs-tools/src/modules/type-definitions/typedoc-adapter.service.ts @@ -64,6 +64,7 @@ export class SkyDocsTypeDocAdapterService { const definition: SkyDocsClassDefinition = { anchorId: this.getAnchorId(entry), name: entry.name, + hasPreviewFeatures: false, }; const tags = this.getCommentTags(entry.comment); @@ -79,6 +80,10 @@ export class SkyDocsTypeDocAdapterService { definition.properties = properties; } + definition.hasPreviewFeatures = + !!definition.methods?.some((method) => method.isPreview) || + !!definition.properties?.some((property) => property.isPreview); + return definition; } @@ -89,6 +94,7 @@ export class SkyDocsTypeDocAdapterService { anchorId: this.getAnchorId(entry), name: entry.name, selector: this.getSelector(entry), + hasPreviewFeatures: false, }; const tags = this.getCommentTags(entry.comment); @@ -110,6 +116,10 @@ export class SkyDocsTypeDocAdapterService { definition.inputProperties = inputProperties; } + definition.hasPreviewFeatures = + !!definition.inputProperties?.some((property) => property.isPreview) || + !!definition.eventProperties?.some((property) => property.isPreview); + return definition; } @@ -121,6 +131,7 @@ export class SkyDocsTypeDocAdapterService { anchorId: this.getAnchorId(entry), members, name: entry.name, + hasPreviewFeatures: !!members.find((member) => member.isPreview), }; const tags = this.getCommentTags(entry.comment); @@ -136,6 +147,7 @@ export class SkyDocsTypeDocAdapterService { const definition: SkyDocsInterfaceDefinition = { anchorId: this.getAnchorId(entry), name: entry.name, + hasPreviewFeatures: !!properties.find((member) => member.isPreview), properties, }; @@ -173,6 +185,7 @@ export class SkyDocsTypeDocAdapterService { const definition: SkyDocsTypeAliasDefinition = { anchorId: this.getAnchorId(entry), name: entry.name, + isPreview: false, type: this.getTypeDefinition({ comment: entry.comment, type: entry.type, @@ -230,6 +243,7 @@ export class SkyDocsTypeDocAdapterService { .map((child) => { let definition: SkyDocsClassPropertyDefinition = { isOptional: true, + isPreview: false, name: this.getPropertyName(child), type: this.getTypeDefinition(child), }; @@ -352,6 +366,7 @@ export class SkyDocsTypeDocAdapterService { name: this.getPropertyName(child), type: this.getTypeDefinition(child), isStatic: !!child.flags?.isStatic, + isPreview: false, parentName: entry.name, }; @@ -397,6 +412,7 @@ export class SkyDocsTypeDocAdapterService { } const definition: SkyDocsInterfacePropertyDefinition = { isOptional: tags.extras.required ? false : !!child.flags?.isOptional, + isPreview: false, name: this.getPropertyName(child), type: this.getTypeDefinition(child), }; @@ -413,6 +429,7 @@ export class SkyDocsTypeDocAdapterService { const indexSignature = entry.indexSignature; const definition: SkyDocsInterfacePropertyDefinition = { isOptional: true, + isPreview: false, name: indexSignature.name, type: { indexSignature: this.getIndexSignatureDefinition(indexSignature), @@ -442,6 +459,7 @@ export class SkyDocsTypeDocAdapterService { return entry.children.map((child) => { const definition: SkyDocsEnumerationMemberDefinition = { name: child.name, + isPreview: false, }; const tags = this.getCommentTags(child.comment); @@ -713,6 +731,9 @@ export class SkyDocsTypeDocAdapterService { .trim(), }); break; + case '@preview': + extras['preview'] = true; + break; case '@required': extras['required'] = true; break; @@ -774,6 +795,7 @@ export class SkyDocsTypeDocAdapterService { codeExampleLanguage?: string; deprecationWarning?: string; description?: string; + isPreview?: boolean; }, tags: SkyDocsCommentTags ): void { @@ -789,6 +811,10 @@ export class SkyDocsTypeDocAdapterService { if (tags.description) { definition.description = tags.description; } + + if (tags.extras?.preview) { + definition.isPreview = tags.extras?.preview; + } } private getDecorator( diff --git a/projects/docs-tools/src/modules/type-definitions/typedoc-types.ts b/projects/docs-tools/src/modules/type-definitions/typedoc-types.ts index 69025acc..68f2cf92 100644 --- a/projects/docs-tools/src/modules/type-definitions/typedoc-types.ts +++ b/projects/docs-tools/src/modules/type-definitions/typedoc-types.ts @@ -10,6 +10,7 @@ export interface TypeDocComment { | '@deprecated' | '@example' | '@param' + | '@preview' | '@required'; }[]; } diff --git a/projects/docs-tools/src/public-api.ts b/projects/docs-tools/src/public-api.ts index c7a04317..bbbe789a 100644 --- a/projects/docs-tools/src/public-api.ts +++ b/projects/docs-tools/src/public-api.ts @@ -21,6 +21,7 @@ export * from './modules/module-info/module-info.module'; export * from './modules/shared/docs-tools-component-info'; export * from './modules/shared/docs-tools-options'; +export * from './modules/shared/docs-tools-site-options'; export * from './modules/shared/docs-tools-supportal.service'; export * from './modules/source-code/source-code-provider';