@anglr/select 17.2.0 → 18.0.0-beta.20260805050455
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/.claude/skills/angular-developer/SKILL.md +130 -130
- package/.claude/skills/angular-developer/references/angular-animations.md +160 -160
- package/.claude/skills/angular-developer/references/angular-aria.md +410 -410
- package/.claude/skills/angular-developer/references/cli.md +86 -86
- package/.claude/skills/angular-developer/references/component-harnesses.md +59 -59
- package/.claude/skills/angular-developer/references/component-styling.md +91 -91
- package/.claude/skills/angular-developer/references/components.md +117 -117
- package/.claude/skills/angular-developer/references/creating-services.md +97 -97
- package/.claude/skills/angular-developer/references/data-resolvers.md +69 -69
- package/.claude/skills/angular-developer/references/define-routes.md +67 -67
- package/.claude/skills/angular-developer/references/defining-providers.md +72 -72
- package/.claude/skills/angular-developer/references/di-fundamentals.md +120 -120
- package/.claude/skills/angular-developer/references/e2e-testing.md +66 -66
- package/.claude/skills/angular-developer/references/effects.md +83 -83
- package/.claude/skills/angular-developer/references/hierarchical-injectors.md +43 -43
- package/.claude/skills/angular-developer/references/host-elements.md +80 -80
- package/.claude/skills/angular-developer/references/injection-context.md +63 -63
- package/.claude/skills/angular-developer/references/inputs.md +101 -101
- package/.claude/skills/angular-developer/references/linked-signal.md +59 -59
- package/.claude/skills/angular-developer/references/loading-strategies.md +61 -61
- package/.claude/skills/angular-developer/references/mcp.md +106 -106
- package/.claude/skills/angular-developer/references/migrations.md +30 -30
- package/.claude/skills/angular-developer/references/navigate-to-routes.md +69 -69
- package/.claude/skills/angular-developer/references/outputs.md +86 -86
- package/.claude/skills/angular-developer/references/reactive-forms.md +122 -122
- package/.claude/skills/angular-developer/references/rendering-strategies.md +44 -44
- package/.claude/skills/angular-developer/references/resource.md +77 -77
- package/.claude/skills/angular-developer/references/route-animations.md +56 -56
- package/.claude/skills/angular-developer/references/route-guards.md +52 -52
- package/.claude/skills/angular-developer/references/router-lifecycle.md +45 -45
- package/.claude/skills/angular-developer/references/router-testing.md +87 -87
- package/.claude/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
- package/.claude/skills/angular-developer/references/signal-forms.md +897 -897
- package/.claude/skills/angular-developer/references/signals-overview.md +94 -94
- package/.claude/skills/angular-developer/references/tailwind-css.md +69 -69
- package/.claude/skills/angular-developer/references/template-driven-forms.md +114 -114
- package/.claude/skills/angular-developer/references/testing-fundamentals.md +66 -66
- package/.github/instructions/typescript/code-conventions.md +7 -7
- package/.github/instructions/typescript/comments.md +40 -40
- package/.github/instructions/typescript/formatting.md +41 -41
- package/.github/instructions/typescript/naming-conventions.md +6 -6
- package/es2022/extensions/src/extensions/getSearch.js.map +1 -1
- package/es2022/extensions/src/extensions/getSelectedOption.js.map +1 -1
- package/es2022/extensions/src/extensions/getSelectedOptions.js.map +1 -1
- package/es2022/extensions/src/extensions/getValue.js.map +1 -1
- package/es2022/extensions/src/extensions/setReadonly.js.map +1 -1
- package/es2022/extensions/src/extensions/setValue.js.map +1 -1
- package/es2022/extensions/src/index.js.map +1 -1
- package/es2022/src/components/index.js.map +1 -1
- package/es2022/src/components/option/option.component.js.map +1 -1
- package/es2022/src/components/optionGroup/optgroup.component.js.map +1 -1
- package/es2022/src/components/select/select.component.js +2 -2
- package/es2022/src/components/select/select.component.js.map +1 -1
- package/es2022/src/decorators/index.js.map +1 -1
- package/es2022/src/decorators/mergeOptionsAsSignal/mergeOptionsAsSignal.decorator.js.map +1 -1
- package/es2022/src/directives/highlightInstance/highlightInstance.directive.js.map +1 -1
- package/es2022/src/directives/highlightSearch/highlightSearch.directive.js.map +1 -1
- package/es2022/src/directives/highlightText/highlightText.directive.js.map +1 -1
- package/es2022/src/directives/index.js.map +1 -1
- package/es2022/src/directives/normalStateTagTemplate/normalStateTagTemplate.directive.js.map +1 -1
- package/es2022/src/directives/normalStateTemplate/normalStateTemplate.directive.js.map +1 -1
- package/es2022/src/directives/optionGroupTemplate/optionGroupTemplate.directive.js.map +1 -1
- package/es2022/src/directives/optionTemplate/optionTemplate.directive.js.map +1 -1
- package/es2022/src/directives/selectAbsolute/selectAbsolute.directive.js.map +1 -1
- package/es2022/src/directives/selectControlValueAccessor/selectControlValueAccessor.directive.js.map +1 -1
- package/es2022/src/directives/selectEdit/selectEdit.directive.js.map +1 -1
- package/es2022/src/directives/selectFormControl/selectFormControl.directive.js.map +1 -1
- package/es2022/src/directives/selectMultipleKeepPopup/selectMultipleKeepPopup.directive.js.map +1 -1
- package/es2022/src/directives/selectNoCarret/selectNoCarret.directive.js.map +1 -1
- package/es2022/src/directives/selectPlaceholder/selectPlaceholder.directive.js.map +1 -1
- package/es2022/src/directives/withDirectAccess/withDirectAccess.directive.js.map +1 -1
- package/es2022/src/index.js.map +1 -1
- package/es2022/src/interfaces/index.js.map +1 -1
- package/es2022/src/interfaces/initState/initState.interface.js.map +1 -1
- package/es2022/src/interfaces/keyboardAction/keyboardAction.interface.js.map +1 -1
- package/es2022/src/interfaces/normalStateContext/normalStateContext.interface.js.map +1 -1
- package/es2022/src/interfaces/normalStateTagContext/normalStateTagContext.interface.js.map +1 -1
- package/es2022/src/interfaces/optionsGatherer/optionsGatherer.interface.js.map +1 -1
- package/es2022/src/interfaces/pluginDescription/pluginDescription.interface.js.map +1 -1
- package/es2022/src/interfaces/pluginOptions/pluginOptions.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/index.js.map +1 -1
- package/es2022/src/interfaces/plugins/interactions/interactions.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/keyboardHandler/keyboardHandler.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/keyboardHandler/simpleKeyboardHandler.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/liveSearch/editLiveSearch.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/liveSearch/filterLiveSearch.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/liveSearch/liveSearch.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/normalState/editNormalState.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/normalState/normalState.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/normalState/simpleNormalState.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/optionsHandler/optionsHandler.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/optionsHandler/simpleOptionsHandler.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/popup/popup.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/popup/simplePopup.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/positioner/commonPositioner.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/positioner/popoverPositioner.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/positioner/positioner.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/readonlyState/readonlyState.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/valueHandler/dynamicValueHandler.interface.js.map +1 -1
- package/es2022/src/interfaces/plugins/valueHandler/valueHandler.interface.js.map +1 -1
- package/es2022/src/interfaces/popupContext/popupContext.interface.js.map +1 -1
- package/es2022/src/interfaces/selectApi/selectApi.interface.js.map +1 -1
- package/es2022/src/interfaces/selectBusOptions/selectBusOptions.interface.js.map +1 -1
- package/es2022/src/interfaces/selectCssClasses/selectCssClasses.interface.js.map +1 -1
- package/es2022/src/interfaces/selectEvent/selectEvent.interface.js.map +1 -1
- package/es2022/src/interfaces/selectEvents/selectEvents.interface.js.map +1 -1
- package/es2022/src/interfaces/selectOption/selectOption.interface.js.map +1 -1
- package/es2022/src/interfaces/selectOptionGroup/selectOptionGroup.interface.js.map +1 -1
- package/es2022/src/interfaces/selectOptionState/selectOptionState.interface.js.map +1 -1
- package/es2022/src/interfaces/selectOptionStateSyntetic/selectOptionStateSyntetic.interface.js.map +1 -1
- package/es2022/src/interfaces/selectOptions/selectOptions.interface.js.map +1 -1
- package/es2022/src/interfaces/selectPlugin/selectPlugin.interface.js.map +1 -1
- package/es2022/src/interfaces/selectPluginTypes/selectPluginTypes.interface.js.map +1 -1
- package/es2022/src/interfaces/tempValue/tempValue.interface.js.map +1 -1
- package/es2022/src/interfaces/templateGatherer/templateGatherer.interface.js.map +1 -1
- package/es2022/src/interfaces/visualPluginOptions/visualPluginOptions.interface.js.map +1 -1
- package/es2022/src/misc/classes/codeOptionsGatherer/codeOptionsGatherer.interface.js.map +1 -1
- package/es2022/src/misc/classes/index.js.map +1 -1
- package/es2022/src/misc/classes/selectBus/selectBus.js.map +1 -1
- package/es2022/src/misc/classes/selectPluginInstances/selectPluginInstances.js.map +1 -1
- package/es2022/src/misc/enums.js.map +1 -1
- package/es2022/src/misc/providers.js.map +1 -1
- package/es2022/src/misc/tokens.js.map +1 -1
- package/es2022/src/misc/types.js.map +1 -1
- package/es2022/src/misc/utils.js.map +1 -1
- package/es2022/src/modules/index.js.map +1 -1
- package/es2022/src/modules/select.module.js.map +1 -1
- package/es2022/src/pipes/addNewOption/addNewOption.pipe.js.map +1 -1
- package/es2022/src/pipes/displayOptionValue/displayOptionValue.pipe.js.map +1 -1
- package/es2022/src/pipes/displayValue/displayValue.pipe.js.map +1 -1
- package/es2022/src/pipes/getPlugin/getPlugin.pipe.js.map +1 -1
- package/es2022/src/pipes/groupedListOptions/groupedListOptions.pipe.js.map +1 -1
- package/es2022/src/pipes/hasValue/hasValue.pipe.js.map +1 -1
- package/es2022/src/pipes/index.js.map +1 -1
- package/es2022/src/pipes/optionCssClasses/optionCssClasses.pipe.js.map +1 -1
- package/es2022/src/plugins/index.js.map +1 -1
- package/es2022/src/plugins/interactions/index.js.map +1 -1
- package/es2022/src/plugins/interactions/simpleInteractions/simpleInteractions.component.js.map +1 -1
- package/es2022/src/plugins/keyboardHandler/index.js.map +1 -1
- package/es2022/src/plugins/keyboardHandler/simpleKeyboardHandler/simpleKeyboardHandler.component.js.map +1 -1
- package/es2022/src/plugins/liveSearch/editLiveSearch/editLiveSearch.component.js +2 -2
- package/es2022/src/plugins/liveSearch/editLiveSearch/editLiveSearch.component.js.map +1 -1
- package/es2022/src/plugins/liveSearch/filterLiveSearch/filterLiveSearch.component.js.map +1 -1
- package/es2022/src/plugins/liveSearch/index.js.map +1 -1
- package/es2022/src/plugins/liveSearch/noLiveSearch/noLiveSearch.component.js.map +1 -1
- package/es2022/src/plugins/normalState/editNormalState/editNormalState.component.js +3 -2
- package/es2022/src/plugins/normalState/editNormalState/editNormalState.component.js.map +1 -1
- package/es2022/src/plugins/normalState/index.js.map +1 -1
- package/es2022/src/plugins/normalState/simpleNormalState/simpleNormalState.component.js +5 -6
- package/es2022/src/plugins/normalState/simpleNormalState/simpleNormalState.component.js.map +1 -1
- package/es2022/src/plugins/optionsHandler/index.js.map +1 -1
- package/es2022/src/plugins/optionsHandler/noOptionsHandler/noOptionsHandler.component.js.map +1 -1
- package/es2022/src/plugins/optionsHandler/optionsHandlerBase.js.map +1 -1
- package/es2022/src/plugins/optionsHandler/simpleOptionsHandler/simpleOptionsHandler.component.js.map +1 -1
- package/es2022/src/plugins/popup/index.js.map +1 -1
- package/es2022/src/plugins/popup/simplePopup/simplePopup.component.js +2 -2
- package/es2022/src/plugins/popup/simplePopup/simplePopup.component.js.map +1 -1
- package/es2022/src/plugins/positioner/commonPositioner/commonPositioner.component.js.map +1 -1
- package/es2022/src/plugins/positioner/index.js.map +1 -1
- package/es2022/src/plugins/positioner/noPositioner/noPositioner.component.js.map +1 -1
- package/es2022/src/plugins/positioner/popoverPositioner/popoverPositioner.component.js.map +1 -1
- package/es2022/src/plugins/valueHandler/dynamicValueHandler/dynamicValueHandler.component.js.map +1 -1
- package/es2022/src/plugins/valueHandler/index.js.map +1 -1
- package/es2022/src/plugins/valueHandler/staticValueHandler/staticValueHandler.component.js.map +1 -1
- package/package.json +125 -125
- package/src/components/select/select.component.css +5 -5
- package/src/interfaces/plugins/normalState/editNormalState.interface.d.ts +4 -0
- package/src/interfaces/plugins/normalState/editNormalState.interface.d.ts.map +1 -1
- package/src/interfaces/plugins/normalState/simpleNormalState.interface.d.ts +1 -5
- package/src/interfaces/plugins/normalState/simpleNormalState.interface.d.ts.map +1 -1
- package/src/plugins/liveSearch/editLiveSearch/editLiveSearch.component.html +19 -19
- package/src/plugins/normalState/editNormalState/editNormalState.component.d.ts.map +1 -1
- package/src/plugins/normalState/editNormalState/editNormalState.component.html +52 -50
- package/src/plugins/normalState/simpleNormalState/simpleNormalState.component.d.ts.map +1 -1
- package/src/plugins/normalState/simpleNormalState/simpleNormalState.component.html +36 -37
- package/src/plugins/popup/simplePopup/simplePopup.component.html +56 -56
- package/styles/components/_liveSearch.scss +32 -32
- package/styles/components/_normalState.scss +121 -116
- package/styles/components/_popup.scss +107 -107
- package/styles/components/_select.scss +14 -14
- package/styles/core/_defaultTheme.scss +105 -105
- package/styles/core/_misc.scss +34 -34
- package/styles/core/_mixins.scss +29 -29
- package/styles/index.scss +2 -2
- package/styles/themes/dark.scss +63 -63
- package/styles/themes/light.scss +64 -64
- package/version.bak +1 -1
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
# Define Routes
|
|
2
|
-
|
|
3
|
-
Routes are objects that define which component should render for a specific URL path.
|
|
4
|
-
|
|
5
|
-
## Basic Configuration
|
|
6
|
-
|
|
7
|
-
Define routes in a `Routes` array and provide them using `provideRouter` in your `appConfig`.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
// app.routes.ts
|
|
11
|
-
export const routes: Routes = [
|
|
12
|
-
{path: '', component: HomePage},
|
|
13
|
-
{path: 'admin', component: AdminPage},
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
// app.config.ts
|
|
17
|
-
export const appConfig: ApplicationConfig = {
|
|
18
|
-
providers: [provideRouter(routes)],
|
|
19
|
-
};
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## URL Paths
|
|
23
|
-
|
|
24
|
-
- **Static**: Matches an exact string (e.g., `'admin'`).
|
|
25
|
-
- **Route Parameters**: Dynamic segments prefixed with a colon (e.g., `'user/:id'`).
|
|
26
|
-
- **Wildcard**: Matches any URL using `**`. Useful for "Not Found" pages. **Always place at the end of the array.**
|
|
27
|
-
|
|
28
|
-
## Matching Strategy
|
|
29
|
-
|
|
30
|
-
Angular uses a **first-match wins** strategy. Specific routes must come before less specific ones.
|
|
31
|
-
|
|
32
|
-
## Redirects
|
|
33
|
-
|
|
34
|
-
Use `redirectTo` to point one path to another.
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
{ path: 'articles', redirectTo: '/blog' },
|
|
38
|
-
{ path: 'blog', component: Blog },
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Page Titles
|
|
42
|
-
|
|
43
|
-
Associate titles with routes for accessibility. Titles can be static or dynamic (via `ResolveFn` or a custom `TitleStrategy`).
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
{ path: 'home', component: Home, title: 'Home Page' }
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Route Data and Providers
|
|
50
|
-
|
|
51
|
-
- **Static Data**: Attach metadata using the `data` property.
|
|
52
|
-
- **Route Providers**: Scope dependencies to a specific route and its children using the `providers` array.
|
|
53
|
-
|
|
54
|
-
## Nested (Child) Routes
|
|
55
|
-
|
|
56
|
-
Define sub-views using the `children` property. Parent components must include a `<router-outlet />`.
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
{
|
|
60
|
-
path: 'product/:id',
|
|
61
|
-
component: Product,
|
|
62
|
-
children: [
|
|
63
|
-
{ path: 'info', component: ProductInfo },
|
|
64
|
-
{ path: 'reviews', component: ProductReviews },
|
|
65
|
-
],
|
|
66
|
-
}
|
|
67
|
-
```
|
|
1
|
+
# Define Routes
|
|
2
|
+
|
|
3
|
+
Routes are objects that define which component should render for a specific URL path.
|
|
4
|
+
|
|
5
|
+
## Basic Configuration
|
|
6
|
+
|
|
7
|
+
Define routes in a `Routes` array and provide them using `provideRouter` in your `appConfig`.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
// app.routes.ts
|
|
11
|
+
export const routes: Routes = [
|
|
12
|
+
{path: '', component: HomePage},
|
|
13
|
+
{path: 'admin', component: AdminPage},
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// app.config.ts
|
|
17
|
+
export const appConfig: ApplicationConfig = {
|
|
18
|
+
providers: [provideRouter(routes)],
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## URL Paths
|
|
23
|
+
|
|
24
|
+
- **Static**: Matches an exact string (e.g., `'admin'`).
|
|
25
|
+
- **Route Parameters**: Dynamic segments prefixed with a colon (e.g., `'user/:id'`).
|
|
26
|
+
- **Wildcard**: Matches any URL using `**`. Useful for "Not Found" pages. **Always place at the end of the array.**
|
|
27
|
+
|
|
28
|
+
## Matching Strategy
|
|
29
|
+
|
|
30
|
+
Angular uses a **first-match wins** strategy. Specific routes must come before less specific ones.
|
|
31
|
+
|
|
32
|
+
## Redirects
|
|
33
|
+
|
|
34
|
+
Use `redirectTo` to point one path to another.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
{ path: 'articles', redirectTo: '/blog' },
|
|
38
|
+
{ path: 'blog', component: Blog },
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Page Titles
|
|
42
|
+
|
|
43
|
+
Associate titles with routes for accessibility. Titles can be static or dynamic (via `ResolveFn` or a custom `TitleStrategy`).
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
{ path: 'home', component: Home, title: 'Home Page' }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Route Data and Providers
|
|
50
|
+
|
|
51
|
+
- **Static Data**: Attach metadata using the `data` property.
|
|
52
|
+
- **Route Providers**: Scope dependencies to a specific route and its children using the `providers` array.
|
|
53
|
+
|
|
54
|
+
## Nested (Child) Routes
|
|
55
|
+
|
|
56
|
+
Define sub-views using the `children` property. Parent components must include a `<router-outlet />`.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
{
|
|
60
|
+
path: 'product/:id',
|
|
61
|
+
component: Product,
|
|
62
|
+
children: [
|
|
63
|
+
{ path: 'info', component: ProductInfo },
|
|
64
|
+
{ path: 'reviews', component: ProductReviews },
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
```
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
# Defining Dependency Providers
|
|
2
|
-
|
|
3
|
-
Angular offers automatic and manual ways to provide dependencies to its Dependency Injection (DI) system.
|
|
4
|
-
|
|
5
|
-
## Automatic Provision
|
|
6
|
-
|
|
7
|
-
The most common way to provide a service is using `providedIn: 'root'` on an `@Injectable()`.
|
|
8
|
-
|
|
9
|
-
### InjectionToken
|
|
10
|
-
|
|
11
|
-
Use `InjectionToken` for non-class dependencies (configuration objects, functions, primitives). An `InjectionToken` can also be automatically provided.
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import {InjectionToken} from '@angular/core';
|
|
15
|
-
|
|
16
|
-
export interface AppConfig {
|
|
17
|
-
apiUrl: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config', {
|
|
21
|
-
providedIn: 'root',
|
|
22
|
-
factory: () => ({apiUrl: 'https://api.example.com'}),
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Manual Provision
|
|
27
|
-
|
|
28
|
-
You use the `providers` array when a service lacks `providedIn`, when you want a new instance for a specific component, or when configuring runtime values.
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
@Component({
|
|
32
|
-
providers: [
|
|
33
|
-
// Shorthand for { provide: LocalService, useClass: LocalService }
|
|
34
|
-
LocalService,
|
|
35
|
-
|
|
36
|
-
// useClass: Swap implementations
|
|
37
|
-
{provide: Logger, useClass: BetterLogger},
|
|
38
|
-
|
|
39
|
-
// useValue: Provide static values
|
|
40
|
-
{provide: API_URL_TOKEN, useValue: 'https://api.example.com'},
|
|
41
|
-
|
|
42
|
-
// useFactory: Generate value dynamically
|
|
43
|
-
{
|
|
44
|
-
provide: ApiClient,
|
|
45
|
-
useFactory: (http = inject(HttpClient)) => new ApiClient(http),
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
// useExisting: Create an alias
|
|
49
|
-
{provide: OldLogger, useExisting: NewLogger},
|
|
50
|
-
|
|
51
|
-
// multi: Provide multiple values for the same token as an array
|
|
52
|
-
{provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true},
|
|
53
|
-
],
|
|
54
|
-
})
|
|
55
|
-
export class Example {}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Scopes of Providers
|
|
59
|
-
|
|
60
|
-
- **Application Bootstrap**: Global singletons. Use for HTTP clients, logging, or app-wide config.
|
|
61
|
-
- **Component/Directive**: Isolated instances. Use for component-specific state or forms. Services are destroyed when the component is destroyed.
|
|
62
|
-
- **Route**: Feature-specific services loaded only with specific routes.
|
|
63
|
-
|
|
64
|
-
## Library Pattern: `provide*` functions
|
|
65
|
-
|
|
66
|
-
Library authors should export functions that return provider arrays to encapsulate configuration:
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
export function provideAnalytics(config: AnalyticsConfig): Provider[] {
|
|
70
|
-
return [{provide: ANALYTICS_CONFIG, useValue: config}, AnalyticsService];
|
|
71
|
-
}
|
|
72
|
-
```
|
|
1
|
+
# Defining Dependency Providers
|
|
2
|
+
|
|
3
|
+
Angular offers automatic and manual ways to provide dependencies to its Dependency Injection (DI) system.
|
|
4
|
+
|
|
5
|
+
## Automatic Provision
|
|
6
|
+
|
|
7
|
+
The most common way to provide a service is using `providedIn: 'root'` on an `@Injectable()`.
|
|
8
|
+
|
|
9
|
+
### InjectionToken
|
|
10
|
+
|
|
11
|
+
Use `InjectionToken` for non-class dependencies (configuration objects, functions, primitives). An `InjectionToken` can also be automatically provided.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {InjectionToken} from '@angular/core';
|
|
15
|
+
|
|
16
|
+
export interface AppConfig {
|
|
17
|
+
apiUrl: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config', {
|
|
21
|
+
providedIn: 'root',
|
|
22
|
+
factory: () => ({apiUrl: 'https://api.example.com'}),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Manual Provision
|
|
27
|
+
|
|
28
|
+
You use the `providers` array when a service lacks `providedIn`, when you want a new instance for a specific component, or when configuring runtime values.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
@Component({
|
|
32
|
+
providers: [
|
|
33
|
+
// Shorthand for { provide: LocalService, useClass: LocalService }
|
|
34
|
+
LocalService,
|
|
35
|
+
|
|
36
|
+
// useClass: Swap implementations
|
|
37
|
+
{provide: Logger, useClass: BetterLogger},
|
|
38
|
+
|
|
39
|
+
// useValue: Provide static values
|
|
40
|
+
{provide: API_URL_TOKEN, useValue: 'https://api.example.com'},
|
|
41
|
+
|
|
42
|
+
// useFactory: Generate value dynamically
|
|
43
|
+
{
|
|
44
|
+
provide: ApiClient,
|
|
45
|
+
useFactory: (http = inject(HttpClient)) => new ApiClient(http),
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// useExisting: Create an alias
|
|
49
|
+
{provide: OldLogger, useExisting: NewLogger},
|
|
50
|
+
|
|
51
|
+
// multi: Provide multiple values for the same token as an array
|
|
52
|
+
{provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true},
|
|
53
|
+
],
|
|
54
|
+
})
|
|
55
|
+
export class Example {}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Scopes of Providers
|
|
59
|
+
|
|
60
|
+
- **Application Bootstrap**: Global singletons. Use for HTTP clients, logging, or app-wide config.
|
|
61
|
+
- **Component/Directive**: Isolated instances. Use for component-specific state or forms. Services are destroyed when the component is destroyed.
|
|
62
|
+
- **Route**: Feature-specific services loaded only with specific routes.
|
|
63
|
+
|
|
64
|
+
## Library Pattern: `provide*` functions
|
|
65
|
+
|
|
66
|
+
Library authors should export functions that return provider arrays to encapsulate configuration:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export function provideAnalytics(config: AnalyticsConfig): Provider[] {
|
|
70
|
+
return [{provide: ANALYTICS_CONFIG, useValue: config}, AnalyticsService];
|
|
71
|
+
}
|
|
72
|
+
```
|
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
# Dependency Injection (DI) Fundamentals
|
|
2
|
-
|
|
3
|
-
Dependency Injection (DI) is a design pattern used to organize and share code across an application by allowing you to "inject" features into different parts. This improves code maintainability, scalability, and testability.
|
|
4
|
-
|
|
5
|
-
## How DI Works in Angular
|
|
6
|
-
|
|
7
|
-
There are two primary ways code interacts with Angular's DI system:
|
|
8
|
-
|
|
9
|
-
1. **Providing**: Making values (objects, functions, primitives) available to the DI system.
|
|
10
|
-
2. **Injecting**: Asking the DI system for those values.
|
|
11
|
-
|
|
12
|
-
Angular components, directives, and services automatically participate in DI.
|
|
13
|
-
|
|
14
|
-
## Services
|
|
15
|
-
|
|
16
|
-
A **service** is the most common way to share data and functionality across an application. It is a TypeScript class decorated with `@Injectable()`.
|
|
17
|
-
|
|
18
|
-
### Creating a Service
|
|
19
|
-
|
|
20
|
-
Use the `providedIn: 'root'` option in the `@Injectable` decorator to make the service a singleton available throughout the entire application. This is the recommended approach for most services.
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import {Injectable} from '@angular/core';
|
|
24
|
-
|
|
25
|
-
@Injectable({
|
|
26
|
-
providedIn: 'root', // Makes this a singleton available everywhere
|
|
27
|
-
})
|
|
28
|
-
export class AnalyticsLogger {
|
|
29
|
-
trackEvent(category: string, value: string) {
|
|
30
|
-
console.log('Analytics event logged:', {category, value});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Common uses for services include:
|
|
36
|
-
|
|
37
|
-
- Data clients (API calls)
|
|
38
|
-
- State management
|
|
39
|
-
- Authentication and authorization
|
|
40
|
-
- Logging and error handling
|
|
41
|
-
- Utility functions
|
|
42
|
-
|
|
43
|
-
## Injecting Dependencies
|
|
44
|
-
|
|
45
|
-
Use Angular's `inject()` function to request dependencies.
|
|
46
|
-
|
|
47
|
-
### The `inject()` Function
|
|
48
|
-
|
|
49
|
-
You can use the `inject()` function to get an instance of a service (or any other provided token).
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
import {Component, inject} from '@angular/core';
|
|
53
|
-
import {Router} from '@angular/router';
|
|
54
|
-
import {AnalyticsLogger} from './analytics-logger.service';
|
|
55
|
-
|
|
56
|
-
@Component({
|
|
57
|
-
selector: 'app-navbar',
|
|
58
|
-
template: `<a href="#" (click)="navigateToDetail($event)">Detail Page</a>`,
|
|
59
|
-
})
|
|
60
|
-
export class Navbar {
|
|
61
|
-
// Injecting dependencies using class field initializers
|
|
62
|
-
private router = inject(Router);
|
|
63
|
-
private analytics = inject(AnalyticsLogger);
|
|
64
|
-
|
|
65
|
-
navigateToDetail(event: Event) {
|
|
66
|
-
event.preventDefault();
|
|
67
|
-
this.analytics.trackEvent('navigation', '/details');
|
|
68
|
-
this.router.navigate(['/details']);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Where can `inject()` be used? (Injection Context)
|
|
74
|
-
|
|
75
|
-
You can call `inject()` in an **injection context**. The most common injection contexts are during the construction of a component, directive, or service.
|
|
76
|
-
|
|
77
|
-
Valid places to call `inject()`:
|
|
78
|
-
|
|
79
|
-
1. **Class field initializers** (Recommended)
|
|
80
|
-
2. **Constructor body**
|
|
81
|
-
3. **Route guards and resolvers** (which are executed in an injection context)
|
|
82
|
-
4. **Factory functions** used in providers
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import {Component, Directive, Injectable, inject, ElementRef} from '@angular/core';
|
|
86
|
-
import {HttpClient} from '@angular/common/http';
|
|
87
|
-
|
|
88
|
-
// 1. In a Component (Field Initializer & Constructor)
|
|
89
|
-
@Component({
|
|
90
|
-
/*...*/
|
|
91
|
-
})
|
|
92
|
-
export class Example {
|
|
93
|
-
private service1 = inject(MyService); // ✅ Field initializer
|
|
94
|
-
|
|
95
|
-
private service2: MyService;
|
|
96
|
-
constructor() {
|
|
97
|
-
this.service2 = inject(MyService); // ✅ Constructor body
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 2. In a Directive
|
|
102
|
-
@Directive({
|
|
103
|
-
/*...*/
|
|
104
|
-
})
|
|
105
|
-
export class MyDirective {
|
|
106
|
-
private element = inject(ElementRef); // ✅ Field initializer
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 3. In a Service
|
|
110
|
-
@Injectable({providedIn: 'root'})
|
|
111
|
-
export class MyService {
|
|
112
|
-
private http = inject(HttpClient); // ✅ Field initializer
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 4. In a Route Guard (Functional)
|
|
116
|
-
export const authGuard = () => {
|
|
117
|
-
const auth = inject(AuthService); // ✅ Route Guard
|
|
118
|
-
return auth.isAuthenticated();
|
|
119
|
-
};
|
|
120
|
-
```
|
|
1
|
+
# Dependency Injection (DI) Fundamentals
|
|
2
|
+
|
|
3
|
+
Dependency Injection (DI) is a design pattern used to organize and share code across an application by allowing you to "inject" features into different parts. This improves code maintainability, scalability, and testability.
|
|
4
|
+
|
|
5
|
+
## How DI Works in Angular
|
|
6
|
+
|
|
7
|
+
There are two primary ways code interacts with Angular's DI system:
|
|
8
|
+
|
|
9
|
+
1. **Providing**: Making values (objects, functions, primitives) available to the DI system.
|
|
10
|
+
2. **Injecting**: Asking the DI system for those values.
|
|
11
|
+
|
|
12
|
+
Angular components, directives, and services automatically participate in DI.
|
|
13
|
+
|
|
14
|
+
## Services
|
|
15
|
+
|
|
16
|
+
A **service** is the most common way to share data and functionality across an application. It is a TypeScript class decorated with `@Injectable()`.
|
|
17
|
+
|
|
18
|
+
### Creating a Service
|
|
19
|
+
|
|
20
|
+
Use the `providedIn: 'root'` option in the `@Injectable` decorator to make the service a singleton available throughout the entire application. This is the recommended approach for most services.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import {Injectable} from '@angular/core';
|
|
24
|
+
|
|
25
|
+
@Injectable({
|
|
26
|
+
providedIn: 'root', // Makes this a singleton available everywhere
|
|
27
|
+
})
|
|
28
|
+
export class AnalyticsLogger {
|
|
29
|
+
trackEvent(category: string, value: string) {
|
|
30
|
+
console.log('Analytics event logged:', {category, value});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Common uses for services include:
|
|
36
|
+
|
|
37
|
+
- Data clients (API calls)
|
|
38
|
+
- State management
|
|
39
|
+
- Authentication and authorization
|
|
40
|
+
- Logging and error handling
|
|
41
|
+
- Utility functions
|
|
42
|
+
|
|
43
|
+
## Injecting Dependencies
|
|
44
|
+
|
|
45
|
+
Use Angular's `inject()` function to request dependencies.
|
|
46
|
+
|
|
47
|
+
### The `inject()` Function
|
|
48
|
+
|
|
49
|
+
You can use the `inject()` function to get an instance of a service (or any other provided token).
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import {Component, inject} from '@angular/core';
|
|
53
|
+
import {Router} from '@angular/router';
|
|
54
|
+
import {AnalyticsLogger} from './analytics-logger.service';
|
|
55
|
+
|
|
56
|
+
@Component({
|
|
57
|
+
selector: 'app-navbar',
|
|
58
|
+
template: `<a href="#" (click)="navigateToDetail($event)">Detail Page</a>`,
|
|
59
|
+
})
|
|
60
|
+
export class Navbar {
|
|
61
|
+
// Injecting dependencies using class field initializers
|
|
62
|
+
private router = inject(Router);
|
|
63
|
+
private analytics = inject(AnalyticsLogger);
|
|
64
|
+
|
|
65
|
+
navigateToDetail(event: Event) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
this.analytics.trackEvent('navigation', '/details');
|
|
68
|
+
this.router.navigate(['/details']);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Where can `inject()` be used? (Injection Context)
|
|
74
|
+
|
|
75
|
+
You can call `inject()` in an **injection context**. The most common injection contexts are during the construction of a component, directive, or service.
|
|
76
|
+
|
|
77
|
+
Valid places to call `inject()`:
|
|
78
|
+
|
|
79
|
+
1. **Class field initializers** (Recommended)
|
|
80
|
+
2. **Constructor body**
|
|
81
|
+
3. **Route guards and resolvers** (which are executed in an injection context)
|
|
82
|
+
4. **Factory functions** used in providers
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import {Component, Directive, Injectable, inject, ElementRef} from '@angular/core';
|
|
86
|
+
import {HttpClient} from '@angular/common/http';
|
|
87
|
+
|
|
88
|
+
// 1. In a Component (Field Initializer & Constructor)
|
|
89
|
+
@Component({
|
|
90
|
+
/*...*/
|
|
91
|
+
})
|
|
92
|
+
export class Example {
|
|
93
|
+
private service1 = inject(MyService); // ✅ Field initializer
|
|
94
|
+
|
|
95
|
+
private service2: MyService;
|
|
96
|
+
constructor() {
|
|
97
|
+
this.service2 = inject(MyService); // ✅ Constructor body
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 2. In a Directive
|
|
102
|
+
@Directive({
|
|
103
|
+
/*...*/
|
|
104
|
+
})
|
|
105
|
+
export class MyDirective {
|
|
106
|
+
private element = inject(ElementRef); // ✅ Field initializer
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 3. In a Service
|
|
110
|
+
@Injectable({providedIn: 'root'})
|
|
111
|
+
export class MyService {
|
|
112
|
+
private http = inject(HttpClient); // ✅ Field initializer
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 4. In a Route Guard (Functional)
|
|
116
|
+
export const authGuard = () => {
|
|
117
|
+
const auth = inject(AuthService); // ✅ Route Guard
|
|
118
|
+
return auth.isAuthenticated();
|
|
119
|
+
};
|
|
120
|
+
```
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
# End-to-End (E2E) Testing
|
|
2
|
-
|
|
3
|
-
This project uses [Cypress](https://www.cypress.io/) for end-to-end (E2E) testing, which simulates real user interactions in a browser. The E2E tests are located primarily within the `devtools/` package.
|
|
4
|
-
|
|
5
|
-
## Running E2E Tests
|
|
6
|
-
|
|
7
|
-
The primary way to run E2E tests is through the `pnpm` script defined in the root `package.json`.
|
|
8
|
-
|
|
9
|
-
1. **Build DevTools:** The E2E tests run against a built version of the devtools extension. You must build it first:
|
|
10
|
-
|
|
11
|
-
```shell
|
|
12
|
-
pnpm -F ng-devtools-mcp build:dev
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
2. **Run Cypress:** Use the `cy:open` or `cy:run` script:
|
|
16
|
-
- To open the interactive Cypress Test Runner:
|
|
17
|
-
```shell
|
|
18
|
-
pnpm -F ng-devtools-mcp cy:open
|
|
19
|
-
```
|
|
20
|
-
- To run the tests headlessly in the terminal (ideal for CI):
|
|
21
|
-
```shell
|
|
22
|
-
pnpm -F ng-devtools-mcp cy:run
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Test Structure
|
|
26
|
-
|
|
27
|
-
- **Configuration:** The main Cypress configuration is located at `devtools/cypress.json`.
|
|
28
|
-
- **Specs:** Test files (specs) are located in `devtools/cypress/integration/`.
|
|
29
|
-
- **Custom Commands:** Reusable custom commands and actions are defined in `devtools/cypress/support/`.
|
|
30
|
-
|
|
31
|
-
### Example E2E Test Snippet
|
|
32
|
-
|
|
33
|
-
A typical test might look like this:
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
// in devtools/cypress/integration/profiler.spec.ts
|
|
37
|
-
|
|
38
|
-
describe('Profiler', () => {
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
cy.visit('/?e2e-app');
|
|
41
|
-
cy.wait(1000);
|
|
42
|
-
cy.get('ng-devtools-tabs').find('a').contains('Profiler').click();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should record and display profiling data', () => {
|
|
46
|
-
// Find the record button and click it
|
|
47
|
-
cy.get('button[aria-label="start-recording-button"]').click();
|
|
48
|
-
|
|
49
|
-
// Interact with the test application to generate profiling data
|
|
50
|
-
cy.get('body').find('#cards button').first().click();
|
|
51
|
-
cy.wait(500);
|
|
52
|
-
|
|
53
|
-
// Stop recording
|
|
54
|
-
cy.get('button[aria-label="stop-recording-button"]').click();
|
|
55
|
-
|
|
56
|
-
// Assert that the flame graph is now visible
|
|
57
|
-
cy.get('ng-devtools-recording-timeline').find('canvas').should('be.visible');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Best Practices
|
|
63
|
-
|
|
64
|
-
- **Use `data-` attributes:** Whenever possible, use `data-cy` or similar attributes for selecting elements to make tests more resilient to CSS or structural changes.
|
|
65
|
-
- **Custom Commands:** Encapsulate common sequences of actions into custom commands in the `support` directory to keep tests clean and readable.
|
|
66
|
-
- **Wait for Application State:** Use `cy.wait()` for arbitrary waits sparingly. Prefer to wait for specific UI elements to appear or for network requests to complete to avoid flaky tests.
|
|
1
|
+
# End-to-End (E2E) Testing
|
|
2
|
+
|
|
3
|
+
This project uses [Cypress](https://www.cypress.io/) for end-to-end (E2E) testing, which simulates real user interactions in a browser. The E2E tests are located primarily within the `devtools/` package.
|
|
4
|
+
|
|
5
|
+
## Running E2E Tests
|
|
6
|
+
|
|
7
|
+
The primary way to run E2E tests is through the `pnpm` script defined in the root `package.json`.
|
|
8
|
+
|
|
9
|
+
1. **Build DevTools:** The E2E tests run against a built version of the devtools extension. You must build it first:
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
pnpm -F ng-devtools-mcp build:dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
2. **Run Cypress:** Use the `cy:open` or `cy:run` script:
|
|
16
|
+
- To open the interactive Cypress Test Runner:
|
|
17
|
+
```shell
|
|
18
|
+
pnpm -F ng-devtools-mcp cy:open
|
|
19
|
+
```
|
|
20
|
+
- To run the tests headlessly in the terminal (ideal for CI):
|
|
21
|
+
```shell
|
|
22
|
+
pnpm -F ng-devtools-mcp cy:run
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Test Structure
|
|
26
|
+
|
|
27
|
+
- **Configuration:** The main Cypress configuration is located at `devtools/cypress.json`.
|
|
28
|
+
- **Specs:** Test files (specs) are located in `devtools/cypress/integration/`.
|
|
29
|
+
- **Custom Commands:** Reusable custom commands and actions are defined in `devtools/cypress/support/`.
|
|
30
|
+
|
|
31
|
+
### Example E2E Test Snippet
|
|
32
|
+
|
|
33
|
+
A typical test might look like this:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// in devtools/cypress/integration/profiler.spec.ts
|
|
37
|
+
|
|
38
|
+
describe('Profiler', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
cy.visit('/?e2e-app');
|
|
41
|
+
cy.wait(1000);
|
|
42
|
+
cy.get('ng-devtools-tabs').find('a').contains('Profiler').click();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should record and display profiling data', () => {
|
|
46
|
+
// Find the record button and click it
|
|
47
|
+
cy.get('button[aria-label="start-recording-button"]').click();
|
|
48
|
+
|
|
49
|
+
// Interact with the test application to generate profiling data
|
|
50
|
+
cy.get('body').find('#cards button').first().click();
|
|
51
|
+
cy.wait(500);
|
|
52
|
+
|
|
53
|
+
// Stop recording
|
|
54
|
+
cy.get('button[aria-label="stop-recording-button"]').click();
|
|
55
|
+
|
|
56
|
+
// Assert that the flame graph is now visible
|
|
57
|
+
cy.get('ng-devtools-recording-timeline').find('canvas').should('be.visible');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Best Practices
|
|
63
|
+
|
|
64
|
+
- **Use `data-` attributes:** Whenever possible, use `data-cy` or similar attributes for selecting elements to make tests more resilient to CSS or structural changes.
|
|
65
|
+
- **Custom Commands:** Encapsulate common sequences of actions into custom commands in the `support` directory to keep tests clean and readable.
|
|
66
|
+
- **Wait for Application State:** Use `cy.wait()` for arbitrary waits sparingly. Prefer to wait for specific UI elements to appear or for network requests to complete to avoid flaky tests.
|