@storybook/angular 9.0.0-beta.6 → 9.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builders/build-storybook/index.mjs +78 -0
- package/dist/builders/build-storybook/index.spec.mjs +187 -0
- package/dist/builders/start-storybook/index.mjs +99 -0
- package/dist/builders/start-storybook/index.spec.mjs +186 -0
- package/dist/builders/utils/error-handler.mjs +33 -0
- package/dist/builders/utils/run-compodoc.mjs +31 -0
- package/dist/builders/utils/run-compodoc.spec.mjs +74 -0
- package/dist/builders/utils/standalone-options.mjs +1 -0
- package/dist/client/angular-beta/AbstractRenderer.mjs +164 -0
- package/dist/client/angular-beta/CanvasRenderer.mjs +9 -0
- package/dist/client/angular-beta/ComputesTemplateFromComponent.mjs +154 -0
- package/dist/client/angular-beta/ComputesTemplateFromComponent.test.mjs +728 -0
- package/dist/client/angular-beta/DocsRenderer.mjs +35 -0
- package/dist/client/angular-beta/RendererFactory.mjs +50 -0
- package/dist/client/angular-beta/RendererFactory.test.mjs +233 -0
- package/dist/client/angular-beta/StorybookModule.mjs +23 -0
- package/dist/client/angular-beta/StorybookModule.test.mjs +319 -0
- package/dist/client/angular-beta/StorybookProvider.mjs +22 -0
- package/dist/client/angular-beta/StorybookWrapperComponent.mjs +123 -0
- package/dist/client/angular-beta/__testfixtures__/input.component.mjs +73 -0
- package/dist/client/angular-beta/__testfixtures__/test.module.mjs +17 -0
- package/dist/client/angular-beta/utils/BootstrapQueue.mjs +49 -0
- package/dist/client/angular-beta/utils/BootstrapQueue.test.mjs +162 -0
- package/dist/client/angular-beta/utils/NgComponentAnalyzer.mjs +84 -0
- package/dist/client/angular-beta/utils/NgComponentAnalyzer.test.mjs +386 -0
- package/dist/client/angular-beta/utils/NgModulesAnalyzer.mjs +37 -0
- package/dist/client/angular-beta/utils/NgModulesAnalyzer.test.mjs +22 -0
- package/dist/client/angular-beta/utils/PropertyExtractor.mjs +158 -0
- package/dist/client/angular-beta/utils/PropertyExtractor.test.mjs +175 -0
- package/dist/client/angular-beta/utils/StoryUID.mjs +38 -0
- package/dist/client/argsToTemplate.mjs +55 -0
- package/dist/client/argsToTemplate.test.mjs +100 -0
- package/dist/client/config.mjs +4 -0
- package/dist/client/decorateStory.mjs +45 -0
- package/dist/client/decorateStory.test.mjs +301 -0
- package/dist/client/decorators.mjs +63 -0
- package/dist/client/decorators.test.mjs +157 -0
- package/dist/client/docs/__testfixtures__/doc-button/input.mjs +201 -0
- package/dist/client/docs/angular-properties.test.mjs +34 -0
- package/dist/client/docs/compodoc.mjs +244 -0
- package/dist/client/docs/compodoc.test.mjs +130 -0
- package/dist/client/docs/config.mjs +16 -0
- package/dist/client/docs/index.mjs +1 -0
- package/dist/client/docs/sourceDecorator.mjs +48 -0
- package/dist/client/docs/types.mjs +1 -0
- package/dist/client/globals.mjs +31 -0
- package/dist/client/index.mjs +9 -0
- package/dist/client/portable-stories.mjs +26 -0
- package/dist/client/preview-prod.mjs +2 -0
- package/dist/client/public-types.mjs +1 -0
- package/dist/client/render.mjs +14 -0
- package/dist/client/types.mjs +1 -0
- package/dist/node/index.mjs +3 -0
- package/dist/server/__mocks-ng-workspace__/minimal-config/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/some-config/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/with-angularBrowserTarget/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/with-nx/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/with-nx-workspace/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/with-options-styles/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/src/main.mjs +2 -0
- package/dist/server/__mocks-ng-workspace__/without-tsConfig/src/main.mjs +2 -0
- package/dist/server/angular-cli-webpack.mjs +80 -0
- package/dist/server/framework-preset-angular-cli.mjs +81 -0
- package/dist/server/framework-preset-angular-docs.mjs +6 -0
- package/dist/server/framework-preset-angular-ivy.mjs +56 -0
- package/dist/server/plugins/storybook-normalize-angular-entry-plugin.mjs +52 -0
- package/dist/server/preset-options.mjs +1 -0
- package/dist/server/utils/filter-out-styling-rules.mjs +13 -0
- package/dist/server/utils/module-is-available.mjs +9 -0
- package/package.json +4 -4
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InjectionToken, NgZone } from '@angular/core';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
export const STORY_PROPS = new InjectionToken('STORY_PROPS');
|
|
4
|
+
export const storyPropsProvider = (storyProps$) => ({
|
|
5
|
+
provide: STORY_PROPS,
|
|
6
|
+
useFactory: storyDataFactory(storyProps$.asObservable()),
|
|
7
|
+
deps: [NgZone],
|
|
8
|
+
});
|
|
9
|
+
function storyDataFactory(data) {
|
|
10
|
+
return (ngZone) => new Observable((subscriber) => {
|
|
11
|
+
const sub = data.subscribe((v) => {
|
|
12
|
+
ngZone.run(() => subscriber.next(v));
|
|
13
|
+
}, (err) => {
|
|
14
|
+
ngZone.run(() => subscriber.error(err));
|
|
15
|
+
}, () => {
|
|
16
|
+
ngZone.run(() => subscriber.complete());
|
|
17
|
+
});
|
|
18
|
+
return () => {
|
|
19
|
+
sub.unsubscribe();
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { ChangeDetectorRef, Component, ElementRef, Inject, NgModule, ViewChild, ViewContainerRef, } from '@angular/core';
|
|
14
|
+
import { Subject } from 'rxjs';
|
|
15
|
+
import { map, skip } from 'rxjs/operators';
|
|
16
|
+
import { STORY_PROPS } from './StorybookProvider';
|
|
17
|
+
import { getComponentInputsOutputs } from './utils/NgComponentAnalyzer';
|
|
18
|
+
import { PropertyExtractor } from './utils/PropertyExtractor';
|
|
19
|
+
const getNonInputsOutputsProps = (ngComponentInputsOutputs, props = {}) => {
|
|
20
|
+
const inputs = ngComponentInputsOutputs.inputs
|
|
21
|
+
.filter((i) => i.templateName in props)
|
|
22
|
+
.map((i) => i.templateName);
|
|
23
|
+
const outputs = ngComponentInputsOutputs.outputs
|
|
24
|
+
.filter((o) => o.templateName in props)
|
|
25
|
+
.map((o) => o.templateName);
|
|
26
|
+
return Object.keys(props).filter((k) => ![...inputs, ...outputs].includes(k));
|
|
27
|
+
};
|
|
28
|
+
/** Wraps the story template into a component */
|
|
29
|
+
export const createStorybookWrapperComponent = ({ selector, template, storyComponent, styles, moduleMetadata, initialProps, analyzedMetadata, }) => {
|
|
30
|
+
// In ivy, a '' selector is not allowed, therefore we need to just set it to anything if
|
|
31
|
+
// storyComponent was not provided.
|
|
32
|
+
const viewChildSelector = storyComponent ?? '__storybook-noop';
|
|
33
|
+
const { imports, declarations, providers } = analyzedMetadata;
|
|
34
|
+
let StorybookComponentModule = class StorybookComponentModule {
|
|
35
|
+
};
|
|
36
|
+
StorybookComponentModule = __decorate([
|
|
37
|
+
NgModule({
|
|
38
|
+
declarations,
|
|
39
|
+
imports,
|
|
40
|
+
exports: [...declarations, ...imports],
|
|
41
|
+
})
|
|
42
|
+
], StorybookComponentModule);
|
|
43
|
+
PropertyExtractor.warnImportsModuleWithProviders(analyzedMetadata);
|
|
44
|
+
let StorybookWrapperComponent = class StorybookWrapperComponent {
|
|
45
|
+
constructor(storyProps$, changeDetectorRef) {
|
|
46
|
+
this.storyProps$ = storyProps$;
|
|
47
|
+
this.changeDetectorRef = changeDetectorRef;
|
|
48
|
+
// Used in case of a component without selector
|
|
49
|
+
this.storyComponent = storyComponent ?? '';
|
|
50
|
+
}
|
|
51
|
+
ngOnInit() {
|
|
52
|
+
// Subscribes to the observable storyProps$ to keep these properties up to date
|
|
53
|
+
this.storyWrapperPropsSubscription = this.storyProps$.subscribe((storyProps = {}) => {
|
|
54
|
+
// All props are added as component properties
|
|
55
|
+
Object.assign(this, storyProps);
|
|
56
|
+
this.changeDetectorRef.detectChanges();
|
|
57
|
+
this.changeDetectorRef.markForCheck();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
ngAfterViewInit() {
|
|
61
|
+
// Bind properties to component, if the story have component
|
|
62
|
+
if (this.storyComponentElementRef) {
|
|
63
|
+
const ngComponentInputsOutputs = getComponentInputsOutputs(storyComponent);
|
|
64
|
+
const initialOtherProps = getNonInputsOutputsProps(ngComponentInputsOutputs, initialProps);
|
|
65
|
+
// Initializes properties that are not Inputs | Outputs
|
|
66
|
+
// Allows story props to override local component properties
|
|
67
|
+
initialOtherProps.forEach((p) => {
|
|
68
|
+
this.storyComponentElementRef[p] = initialProps[p];
|
|
69
|
+
});
|
|
70
|
+
// `markForCheck` the component in case this uses changeDetection: OnPush
|
|
71
|
+
// And then forces the `detectChanges`
|
|
72
|
+
this.storyComponentViewContainerRef.injector.get(ChangeDetectorRef).markForCheck();
|
|
73
|
+
this.changeDetectorRef.detectChanges();
|
|
74
|
+
// Once target component has been initialized, the storyProps$ observable keeps target component properties than are not Input|Output up to date
|
|
75
|
+
this.storyComponentPropsSubscription = this.storyProps$
|
|
76
|
+
.pipe(skip(1), map((props) => {
|
|
77
|
+
const propsKeyToKeep = getNonInputsOutputsProps(ngComponentInputsOutputs, props);
|
|
78
|
+
return propsKeyToKeep.reduce((acc, p) => ({ ...acc, [p]: props[p] }), {});
|
|
79
|
+
}))
|
|
80
|
+
.subscribe((props) => {
|
|
81
|
+
// Replace inputs with new ones from props
|
|
82
|
+
Object.assign(this.storyComponentElementRef, props);
|
|
83
|
+
// `markForCheck` the component in case this uses changeDetection: OnPush
|
|
84
|
+
// And then forces the `detectChanges`
|
|
85
|
+
this.storyComponentViewContainerRef.injector.get(ChangeDetectorRef).markForCheck();
|
|
86
|
+
this.changeDetectorRef.detectChanges();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
ngOnDestroy() {
|
|
91
|
+
if (this.storyComponentPropsSubscription != null) {
|
|
92
|
+
this.storyComponentPropsSubscription.unsubscribe();
|
|
93
|
+
}
|
|
94
|
+
if (this.storyWrapperPropsSubscription != null) {
|
|
95
|
+
this.storyWrapperPropsSubscription.unsubscribe();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
__decorate([
|
|
100
|
+
ViewChild(viewChildSelector, { static: true }),
|
|
101
|
+
__metadata("design:type", ElementRef)
|
|
102
|
+
], StorybookWrapperComponent.prototype, "storyComponentElementRef", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
ViewChild(viewChildSelector, { read: ViewContainerRef, static: true }),
|
|
105
|
+
__metadata("design:type", ViewContainerRef)
|
|
106
|
+
], StorybookWrapperComponent.prototype, "storyComponentViewContainerRef", void 0);
|
|
107
|
+
StorybookWrapperComponent = __decorate([
|
|
108
|
+
Component({
|
|
109
|
+
selector,
|
|
110
|
+
template,
|
|
111
|
+
standalone: true,
|
|
112
|
+
imports: [StorybookComponentModule],
|
|
113
|
+
providers,
|
|
114
|
+
styles,
|
|
115
|
+
schemas: moduleMetadata.schemas,
|
|
116
|
+
}),
|
|
117
|
+
__param(0, Inject(STORY_PROPS)),
|
|
118
|
+
__param(1, Inject(ChangeDetectorRef)),
|
|
119
|
+
__metadata("design:paramtypes", [Subject,
|
|
120
|
+
ChangeDetectorRef])
|
|
121
|
+
], StorybookWrapperComponent);
|
|
122
|
+
return StorybookWrapperComponent;
|
|
123
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
11
|
+
export const exportedConstant = 'An exported constant';
|
|
12
|
+
export var ButtonAccent;
|
|
13
|
+
(function (ButtonAccent) {
|
|
14
|
+
ButtonAccent["Normal"] = "Normal";
|
|
15
|
+
ButtonAccent["High"] = "High";
|
|
16
|
+
})(ButtonAccent || (ButtonAccent = {}));
|
|
17
|
+
let InputComponent = class InputComponent {
|
|
18
|
+
constructor() {
|
|
19
|
+
/** Appearance style of the button. */
|
|
20
|
+
this.appearance = 'secondary';
|
|
21
|
+
/** Sets the button to a disabled state. */
|
|
22
|
+
this.isDisabled = false;
|
|
23
|
+
this.onClick = new EventEmitter();
|
|
24
|
+
this.dashOut = new EventEmitter();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
__decorate([
|
|
28
|
+
Input(),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], InputComponent.prototype, "appearance", void 0);
|
|
31
|
+
__decorate([
|
|
32
|
+
Input(),
|
|
33
|
+
__metadata("design:type", Number)
|
|
34
|
+
], InputComponent.prototype, "counter", void 0);
|
|
35
|
+
__decorate([
|
|
36
|
+
Input(),
|
|
37
|
+
__metadata("design:type", String)
|
|
38
|
+
], InputComponent.prototype, "accent", void 0);
|
|
39
|
+
__decorate([
|
|
40
|
+
Input('color'),
|
|
41
|
+
__metadata("design:type", String)
|
|
42
|
+
], InputComponent.prototype, "foregroundColor", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
Input(),
|
|
45
|
+
__metadata("design:type", Object)
|
|
46
|
+
], InputComponent.prototype, "isDisabled", void 0);
|
|
47
|
+
__decorate([
|
|
48
|
+
Input(),
|
|
49
|
+
__metadata("design:type", String)
|
|
50
|
+
], InputComponent.prototype, "label", void 0);
|
|
51
|
+
__decorate([
|
|
52
|
+
Input('aria-label'),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], InputComponent.prototype, "ariaLabel", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
Input(),
|
|
57
|
+
__metadata("design:type", Object)
|
|
58
|
+
], InputComponent.prototype, "someDataObject", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
Output(),
|
|
61
|
+
__metadata("design:type", Object)
|
|
62
|
+
], InputComponent.prototype, "onClick", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
Output('dash-out'),
|
|
65
|
+
__metadata("design:type", Object)
|
|
66
|
+
], InputComponent.prototype, "dashOut", void 0);
|
|
67
|
+
InputComponent = __decorate([
|
|
68
|
+
Component({
|
|
69
|
+
selector: 'doc-button',
|
|
70
|
+
template: '<button>{{ label }}</button>',
|
|
71
|
+
})
|
|
72
|
+
], InputComponent);
|
|
73
|
+
export { InputComponent };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { CommonModule } from '@angular/common';
|
|
8
|
+
import { HttpClientModule } from '@angular/common/http';
|
|
9
|
+
import { NgModule } from '@angular/core';
|
|
10
|
+
let WithOfficialModule = class WithOfficialModule {
|
|
11
|
+
};
|
|
12
|
+
WithOfficialModule = __decorate([
|
|
13
|
+
NgModule({
|
|
14
|
+
imports: [CommonModule, HttpClientModule],
|
|
15
|
+
})
|
|
16
|
+
], WithOfficialModule);
|
|
17
|
+
export { WithOfficialModule };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const queue = [];
|
|
2
|
+
let isProcessing = false;
|
|
3
|
+
/**
|
|
4
|
+
* Reset compiled components because we often want to compile the same component with more than one
|
|
5
|
+
* NgModule.
|
|
6
|
+
*/
|
|
7
|
+
const resetCompiledComponents = async () => {
|
|
8
|
+
try {
|
|
9
|
+
// Clear global Angular component cache in order to be able to re-render the same component across multiple stories
|
|
10
|
+
//
|
|
11
|
+
// References:
|
|
12
|
+
// https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-accept.ts#L50
|
|
13
|
+
// https://github.com/angular/angular/blob/2ebe2bcb2fe19bf672316b05f15241fd7fd40803/packages/core/src/render3/jit/module.ts#L377-L384
|
|
14
|
+
const { ɵresetCompiledComponents } = await import('@angular/core');
|
|
15
|
+
ɵresetCompiledComponents();
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
/** Noop catch This means angular removed or modified ɵresetCompiledComponents */
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Queue bootstrapping, so that only one application can be bootstrapped at a time.
|
|
23
|
+
*
|
|
24
|
+
* Bootstrapping multiple applications at once can cause Angular to throw an error that a component
|
|
25
|
+
* is declared in multiple modules. This avoids two stories confusing the Angular compiler, by
|
|
26
|
+
* bootstrapping more that one application at a time.
|
|
27
|
+
*
|
|
28
|
+
* @param fn Callback that should complete the bootstrap process
|
|
29
|
+
* @returns ApplicationRef from the completed bootstrap process
|
|
30
|
+
*/
|
|
31
|
+
export const queueBootstrapping = (fn) => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
queue.push(() => fn().then(resolve).catch(reject));
|
|
34
|
+
if (!isProcessing) {
|
|
35
|
+
processQueue();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const processQueue = async () => {
|
|
40
|
+
isProcessing = true;
|
|
41
|
+
while (queue.length > 0) {
|
|
42
|
+
const bootstrappingFn = queue.shift();
|
|
43
|
+
if (bootstrappingFn) {
|
|
44
|
+
await bootstrappingFn();
|
|
45
|
+
await resetCompiledComponents();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
isProcessing = false;
|
|
49
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { Subject, lastValueFrom } from 'rxjs';
|
|
4
|
+
import { queueBootstrapping } from './BootstrapQueue';
|
|
5
|
+
const instantWaitFor = (fn) => {
|
|
6
|
+
return vi.waitFor(fn, {
|
|
7
|
+
interval: 0,
|
|
8
|
+
timeout: 10000,
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
describe('BootstrapQueue', { retry: 3 }, () => {
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
it('@flaky should wait until complete', async () => {
|
|
19
|
+
const pendingSubject = new Subject();
|
|
20
|
+
const bootstrapApp = vi.fn().mockImplementation(async () => {
|
|
21
|
+
return lastValueFrom(pendingSubject);
|
|
22
|
+
});
|
|
23
|
+
const bootstrapAppFinished = vi.fn();
|
|
24
|
+
queueBootstrapping(bootstrapApp).then(() => {
|
|
25
|
+
bootstrapAppFinished();
|
|
26
|
+
});
|
|
27
|
+
await instantWaitFor(() => {
|
|
28
|
+
if (bootstrapApp.mock.calls.length !== 1) {
|
|
29
|
+
throw new Error('bootstrapApp should not have been called yet');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
expect(bootstrapApp).toHaveBeenCalled();
|
|
33
|
+
expect(bootstrapAppFinished).not.toHaveBeenCalled();
|
|
34
|
+
pendingSubject.next();
|
|
35
|
+
pendingSubject.complete();
|
|
36
|
+
await instantWaitFor(() => {
|
|
37
|
+
if (bootstrapAppFinished.mock.calls.length !== 1) {
|
|
38
|
+
throw new Error('bootstrapApp should have been called once');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
expect(bootstrapAppFinished).toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
it('should prevent following tasks, until the preview tasks are complete', async () => {
|
|
44
|
+
const pendingSubject = new Subject();
|
|
45
|
+
const bootstrapApp = vi.fn().mockImplementation(async () => {
|
|
46
|
+
return lastValueFrom(pendingSubject);
|
|
47
|
+
});
|
|
48
|
+
const bootstrapAppFinished = vi.fn();
|
|
49
|
+
const pendingSubject2 = new Subject();
|
|
50
|
+
const bootstrapApp2 = vi.fn().mockImplementation(async () => {
|
|
51
|
+
return lastValueFrom(pendingSubject2);
|
|
52
|
+
});
|
|
53
|
+
const bootstrapAppFinished2 = vi.fn();
|
|
54
|
+
const pendingSubject3 = new Subject();
|
|
55
|
+
const bootstrapApp3 = vi.fn().mockImplementation(async () => {
|
|
56
|
+
return lastValueFrom(pendingSubject3);
|
|
57
|
+
});
|
|
58
|
+
const bootstrapAppFinished3 = vi.fn();
|
|
59
|
+
queueBootstrapping(bootstrapApp).then(bootstrapAppFinished);
|
|
60
|
+
queueBootstrapping(bootstrapApp2).then(bootstrapAppFinished2);
|
|
61
|
+
queueBootstrapping(bootstrapApp3).then(bootstrapAppFinished3);
|
|
62
|
+
await instantWaitFor(() => {
|
|
63
|
+
if (bootstrapApp.mock.calls.length !== 1) {
|
|
64
|
+
throw new Error('bootstrapApp should have been called once');
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
expect(bootstrapApp).toHaveBeenCalled();
|
|
68
|
+
expect(bootstrapAppFinished).not.toHaveBeenCalled();
|
|
69
|
+
expect(bootstrapApp2).not.toHaveBeenCalled();
|
|
70
|
+
expect(bootstrapAppFinished2).not.toHaveBeenCalled();
|
|
71
|
+
expect(bootstrapApp3).not.toHaveBeenCalled();
|
|
72
|
+
expect(bootstrapAppFinished3).not.toHaveBeenCalled();
|
|
73
|
+
pendingSubject.next();
|
|
74
|
+
pendingSubject.complete();
|
|
75
|
+
await instantWaitFor(() => {
|
|
76
|
+
if (bootstrapApp2.mock.calls.length !== 1) {
|
|
77
|
+
throw new Error('bootstrapApp2 should have been called once');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
expect(bootstrapApp).toHaveReturnedTimes(1);
|
|
81
|
+
expect(bootstrapAppFinished).toHaveBeenCalled();
|
|
82
|
+
expect(bootstrapApp2).toHaveBeenCalled();
|
|
83
|
+
expect(bootstrapAppFinished2).not.toHaveBeenCalled();
|
|
84
|
+
expect(bootstrapApp3).not.toHaveBeenCalled();
|
|
85
|
+
expect(bootstrapAppFinished3).not.toHaveBeenCalled();
|
|
86
|
+
pendingSubject2.next();
|
|
87
|
+
pendingSubject2.complete();
|
|
88
|
+
await instantWaitFor(() => {
|
|
89
|
+
if (bootstrapApp3.mock.calls.length !== 1) {
|
|
90
|
+
throw new Error('bootstrapApp3 should have been called once');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
expect(bootstrapApp).toHaveReturnedTimes(1);
|
|
94
|
+
expect(bootstrapAppFinished).toHaveBeenCalled();
|
|
95
|
+
expect(bootstrapApp2).toHaveReturnedTimes(1);
|
|
96
|
+
expect(bootstrapAppFinished2).toHaveBeenCalled();
|
|
97
|
+
expect(bootstrapApp3).toHaveBeenCalled();
|
|
98
|
+
expect(bootstrapAppFinished3).not.toHaveBeenCalled();
|
|
99
|
+
pendingSubject3.next();
|
|
100
|
+
pendingSubject3.complete();
|
|
101
|
+
await instantWaitFor(() => {
|
|
102
|
+
if (bootstrapAppFinished3.mock.calls.length !== 1) {
|
|
103
|
+
throw new Error('bootstrapAppFinished3 should have been called once');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
expect(bootstrapApp).toHaveReturnedTimes(1);
|
|
107
|
+
expect(bootstrapAppFinished).toHaveBeenCalled();
|
|
108
|
+
expect(bootstrapApp2).toHaveReturnedTimes(1);
|
|
109
|
+
expect(bootstrapAppFinished2).toHaveBeenCalled();
|
|
110
|
+
expect(bootstrapApp3).toHaveReturnedTimes(1);
|
|
111
|
+
expect(bootstrapAppFinished3).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
it('should throw and continue next bootstrap on error', async () => {
|
|
114
|
+
const pendingSubject = new Subject();
|
|
115
|
+
const bootstrapApp = vi.fn().mockImplementation(async () => {
|
|
116
|
+
return lastValueFrom(pendingSubject);
|
|
117
|
+
});
|
|
118
|
+
const bootstrapAppFinished = vi.fn();
|
|
119
|
+
const bootstrapAppError = vi.fn();
|
|
120
|
+
const pendingSubject2 = new Subject();
|
|
121
|
+
const bootstrapApp2 = vi.fn().mockImplementation(async () => {
|
|
122
|
+
return lastValueFrom(pendingSubject2);
|
|
123
|
+
});
|
|
124
|
+
const bootstrapAppFinished2 = vi.fn();
|
|
125
|
+
const bootstrapAppError2 = vi.fn();
|
|
126
|
+
queueBootstrapping(bootstrapApp).then(bootstrapAppFinished).catch(bootstrapAppError);
|
|
127
|
+
queueBootstrapping(bootstrapApp2).then(bootstrapAppFinished2).catch(bootstrapAppError2);
|
|
128
|
+
await instantWaitFor(() => {
|
|
129
|
+
if (bootstrapApp.mock.calls.length !== 1) {
|
|
130
|
+
throw new Error('bootstrapApp should have been called once');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
expect(bootstrapApp).toHaveBeenCalledTimes(1);
|
|
134
|
+
expect(bootstrapAppFinished).not.toHaveBeenCalled();
|
|
135
|
+
expect(bootstrapApp2).not.toHaveBeenCalled();
|
|
136
|
+
pendingSubject.error(new Error('test error'));
|
|
137
|
+
await instantWaitFor(() => {
|
|
138
|
+
if (bootstrapAppError.mock.calls.length !== 1) {
|
|
139
|
+
throw new Error('bootstrapAppError should have been called once');
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
expect(bootstrapApp).toHaveBeenCalledTimes(1);
|
|
143
|
+
expect(bootstrapAppFinished).not.toHaveBeenCalled();
|
|
144
|
+
expect(bootstrapAppError).toHaveBeenCalledTimes(1);
|
|
145
|
+
expect(bootstrapApp2).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(bootstrapAppFinished2).not.toHaveBeenCalled();
|
|
147
|
+
expect(bootstrapAppError2).not.toHaveBeenCalled();
|
|
148
|
+
pendingSubject2.next();
|
|
149
|
+
pendingSubject2.complete();
|
|
150
|
+
await instantWaitFor(() => {
|
|
151
|
+
if (bootstrapAppFinished2.mock.calls.length !== 1) {
|
|
152
|
+
throw new Error('bootstrapAppFinished2 should have been called once');
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
expect(bootstrapApp).toHaveBeenCalledTimes(1);
|
|
156
|
+
expect(bootstrapAppFinished).not.toHaveBeenCalled();
|
|
157
|
+
expect(bootstrapAppError).toHaveBeenCalledTimes(1);
|
|
158
|
+
expect(bootstrapApp2).toHaveBeenCalledTimes(1);
|
|
159
|
+
expect(bootstrapAppFinished2).toHaveBeenCalledTimes(1);
|
|
160
|
+
expect(bootstrapAppError2).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Component, Directive, Input, Output, Pipe, ɵReflectionCapabilities as ReflectionCapabilities, } from '@angular/core';
|
|
2
|
+
const reflectionCapabilities = new ReflectionCapabilities();
|
|
3
|
+
/** Returns component Inputs / Outputs by browsing these properties and decorator */
|
|
4
|
+
export const getComponentInputsOutputs = (component) => {
|
|
5
|
+
const componentMetadata = getComponentDecoratorMetadata(component);
|
|
6
|
+
const componentPropsMetadata = getComponentPropsDecoratorMetadata(component);
|
|
7
|
+
const initialValue = {
|
|
8
|
+
inputs: [],
|
|
9
|
+
outputs: [],
|
|
10
|
+
};
|
|
11
|
+
// Adds the I/O present in @Component metadata
|
|
12
|
+
if (componentMetadata && componentMetadata.inputs) {
|
|
13
|
+
initialValue.inputs.push(...componentMetadata.inputs.map((i) => ({
|
|
14
|
+
propName: typeof i === 'string' ? i : i.name,
|
|
15
|
+
templateName: typeof i === 'string' ? i : i.alias,
|
|
16
|
+
})));
|
|
17
|
+
}
|
|
18
|
+
if (componentMetadata && componentMetadata.outputs) {
|
|
19
|
+
initialValue.outputs.push(...componentMetadata.outputs.map((i) => ({ propName: i, templateName: i })));
|
|
20
|
+
}
|
|
21
|
+
if (!componentPropsMetadata) {
|
|
22
|
+
return initialValue;
|
|
23
|
+
}
|
|
24
|
+
// Browses component properties to extract I/O
|
|
25
|
+
// Filters properties that have the same name as the one present in the @Component property
|
|
26
|
+
return Object.entries(componentPropsMetadata).reduce((previousValue, [propertyName, values]) => {
|
|
27
|
+
const value = values.find((v) => v instanceof Input || v instanceof Output);
|
|
28
|
+
if (value instanceof Input) {
|
|
29
|
+
const inputToAdd = {
|
|
30
|
+
propName: propertyName,
|
|
31
|
+
templateName: value.bindingPropertyName ?? value.alias ?? propertyName,
|
|
32
|
+
};
|
|
33
|
+
const previousInputsFiltered = previousValue.inputs.filter((i) => i.templateName !== propertyName);
|
|
34
|
+
return {
|
|
35
|
+
...previousValue,
|
|
36
|
+
inputs: [...previousInputsFiltered, inputToAdd],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (value instanceof Output) {
|
|
40
|
+
const outputToAdd = {
|
|
41
|
+
propName: propertyName,
|
|
42
|
+
templateName: value.bindingPropertyName ?? value.alias ?? propertyName,
|
|
43
|
+
};
|
|
44
|
+
const previousOutputsFiltered = previousValue.outputs.filter((i) => i.templateName !== propertyName);
|
|
45
|
+
return {
|
|
46
|
+
...previousValue,
|
|
47
|
+
outputs: [...previousOutputsFiltered, outputToAdd],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return previousValue;
|
|
51
|
+
}, initialValue);
|
|
52
|
+
};
|
|
53
|
+
export const isDeclarable = (component) => {
|
|
54
|
+
if (!component) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const decorators = reflectionCapabilities.annotations(component);
|
|
58
|
+
return !!(decorators || []).find((d) => d instanceof Directive || d instanceof Pipe || d instanceof Component);
|
|
59
|
+
};
|
|
60
|
+
export const isComponent = (component) => {
|
|
61
|
+
if (!component) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const decorators = reflectionCapabilities.annotations(component);
|
|
65
|
+
return (decorators || []).some((d) => d instanceof Component);
|
|
66
|
+
};
|
|
67
|
+
export const isStandaloneComponent = (component) => {
|
|
68
|
+
if (!component) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const decorators = reflectionCapabilities.annotations(component);
|
|
72
|
+
// TODO: `standalone` is only available in Angular v14. Remove cast to `any` once
|
|
73
|
+
// Angular deps are updated to v14.x.x.
|
|
74
|
+
return (decorators || []).some((d) => (d instanceof Component || d instanceof Directive || d instanceof Pipe) && d.standalone);
|
|
75
|
+
};
|
|
76
|
+
/** Returns all component decorator properties is used to get all `@Input` and `@Output` Decorator */
|
|
77
|
+
export const getComponentPropsDecoratorMetadata = (component) => {
|
|
78
|
+
return reflectionCapabilities.propMetadata(component);
|
|
79
|
+
};
|
|
80
|
+
/** Returns component decorator `@Component` */
|
|
81
|
+
export const getComponentDecoratorMetadata = (component) => {
|
|
82
|
+
const decorators = reflectionCapabilities.annotations(component);
|
|
83
|
+
return decorators.reverse().find((d) => d instanceof Component);
|
|
84
|
+
};
|