refactor(docs-infra): remove example code that nothing references

The `built-in-directives` and `structural-directives` example apps are no
longer referenced. Two guide rewrites moved their pages to inline code fences
and dropped the last references without removing the apps: #69134 for
structural directives, and #69822 for built-in directives, whose page is gone
entirely. Both apps sat inside the `embeddable` glob, so they were still
compiled as preview components on every build.

The example e2e suites are excluded from every consumer by design: previews
ignore `*.e2e-spec.ts` and `*.po.ts`, and the zip and StackBlitz pipelines
both list the suffix in `EXCLUDE_FILES`. They could not run in any case,
since every spec declares a `driver` and never assigns it before calling
`driver.get('')`, left over from an abandoned protractor to selenium
migration.

The eslint configuration in the same directory is also unused. Nothing in the
repository depends on eslint, and `tsconfig.eslint.json` extends a path that
no longer exists.

(cherry picked from commit 90fc9475f2)
This commit is contained in:
Kam
2026-08-24 16:49:11 +03:00
committed by leonsenft
parent 72559b4710
commit 745cc30139
53 changed files with 0 additions and 3040 deletions
-212
View File
@@ -1,212 +0,0 @@
{
"root": true,
"ignorePatterns": ["*.js", "styleguide/**/*.avoid.ts"],
"overrides": [
{
"files": ["*.html"],
"extends": ["plugin:@angular-eslint/template/recommended"],
"rules": {
"@angular-eslint/template/alt-text": "error",
"@angular-eslint/template/elements-content": "error",
"@angular-eslint/template/label-has-associated-control": "error",
"@angular-eslint/template/table-scope": "error",
"@angular-eslint/template/valid-aria": "error",
"@angular-eslint/template/click-events-have-key-events": "error",
"@angular-eslint/template/eqeqeq": "off",
"@angular-eslint/template/mouse-events-have-key-events": "error",
"@angular-eslint/template/no-autofocus": "error",
"@angular-eslint/template/no-distracting-elements": "error",
"@angular-eslint/template/no-positive-tabindex": "error"
}
},
{
"files": ["*.ts"],
"extends": [
"plugin:@angular-eslint/template/process-inline-templates"
],
"parserOptions": {
"createDefaultProgram": false,
"project": "content/examples/tsconfig.eslint.json"
},
"plugins": [
"@typescript-eslint",
"@angular-eslint", "eslint-plugin-import", "eslint-plugin-jsdoc", "eslint-plugin-prefer-arrow"],
"rules": {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"sort-keys": "off",
"@angular-eslint/component-class-suffix": "error",
"@angular-eslint/contextual-lifecycle": "error",
"@angular-eslint/directive-class-suffix": "error",
"@angular-eslint/no-conflicting-lifecycle": "error",
"@angular-eslint/no-host-metadata-property": "error",
"@angular-eslint/no-input-rename": "error",
"@angular-eslint/no-inputs-metadata-property": "error",
"@angular-eslint/no-output-native": "error",
"@angular-eslint/no-output-on-prefix": "error",
"@angular-eslint/no-output-rename": "error",
"@angular-eslint/no-outputs-metadata-property": "error",
"@angular-eslint/use-lifecycle-interface": "error",
"@angular-eslint/use-pipe-transform-interface": "error",
"@typescript-eslint/adjacent-overload-signatures": "error",
"@typescript-eslint/array-type": "off",
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-inferrable-types": [
"error",
{
"ignoreParameters": true
}
],
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-parameter-properties": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/triple-slash-reference": [
"error",
{
"path": "always",
"types": "prefer-import",
"lib": "always"
}
],
"@typescript-eslint/unified-signatures": "error",
"complexity": "off",
"constructor-super": "error",
"eqeqeq": ["error", "smart"],
"guard-for-in": "error",
"id-blacklist": [
"error",
"any",
"Number",
"number",
"String",
"string",
"Boolean",
"boolean",
"Undefined",
"undefined"
],
"id-match": "error",
"import/no-deprecated": "warn",
"jsdoc/no-types": "error",
"max-classes-per-file": "off",
"no-bitwise": "error",
"no-caller": "error",
"no-cond-assign": "error",
"no-debugger": "error",
"no-empty": "off",
"no-eval": "error",
"no-fallthrough": "error",
"no-invalid-this": "off",
"no-new-wrappers": "error",
"no-restricted-imports": [
"error",
{
"name": "rxjs/Rx",
"message": "Please import directly from 'rxjs' instead"
}
],
"no-throw-literal": "error",
"no-undef-init": "error",
"no-unsafe-finally": "error",
"no-unused-labels": "error",
"no-var": "error",
"object-shorthand": "error",
"one-var": ["error", "never"],
"prefer-const": "error",
"radix": "error",
"use-isnan": "error",
"valid-typeof": "off",
"arrow-body-style": "error",
"arrow-parens": "off",
"comma-dangle": "off",
"curly": "error",
"eol-last": "error",
"jsdoc/check-alignment": "error",
"new-parens": "error",
"no-multiple-empty-lines": "off",
"no-trailing-spaces": "error",
"quote-props": ["error", "as-needed"],
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"asyncArrow": "always",
"named": "never"
}
],
"@typescript-eslint/semi": ["error", "always"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/ban-types": "error",
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "", // TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense).
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": ["app", "toh"],
"style": "camelCase"
}
],
"dot-notation": "off",
"@typescript-eslint/dot-notation": ["error", {"allowIndexSignaturePropertyAccess": true}],
"indent": "off",
"max-len": ["error", {"code": 120, "ignoreUrls": true}],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/member-ordering": "off",
"@typescript-eslint/naming-convention": "off",
"no-console": ["error", {"allow": ["log", "warn", "error"]}],
"no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off", // TODO: Fix the code violating this rule and enable it.
"no-restricted-syntax": [
"error",
{
"selector": "CallExpression[callee.name=/^(fdescribe|fit)$/]",
"message": "Don't keep jasmine focus methods."
}
],
"no-shadow": "off",
"@typescript-eslint/no-shadow": "error",
"no-tabs": "error",
"no-underscore-dangle": "off",
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": "error",
"no-use-before-define": "off",
"prefer-arrow/prefer-arrow-functions": "off",
"quotes": "off",
"@typescript-eslint/quotes": [
"error",
"single",
{"avoidEscape": true, "allowTemplateLiterals": true}
],
"semi": "error"
}
}
]
}
@@ -1,26 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Accessibility example e2e tests', () => {
let driver: webdriver.WebDriver;
beforeEach(async () => {
await driver.get('');
});
it('should display Accessibility Example', async () => {
expect(await driver.findElement(webdriver.By.css('h1')).getText()).toEqual(
'Accessibility Example',
);
});
it('should take a number and change progressbar width', async () => {
const input = driver.findElement(webdriver.By.css('input'));
await input.sendKeys('16');
expect(await input.getAttribute('value')).toEqual('16');
expect(
await driver
.findElement(webdriver.By.css('app-example-progressbar div'))
.getCssValue('width'),
).toBe('48px');
});
});
@@ -1,21 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('workspace-project App', () => {
let driver: webdriver.WebDriver;
beforeEach(async () => {
await driver.get('');
});
// Add your e2e tests here
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await driver.manage().logs().get(webdriver.logging.Type.BROWSER);
expect(logs).not.toContain(
jasmine.objectContaining({
level: webdriver.logging.Level.SEVERE,
} as webdriver.logging.Entry),
);
});
});
@@ -1,299 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import * as openClose from './open-close.po';
import * as statusSlider from './status-slider.po';
import * as toggle from './toggle.po';
import * as enterLeave from './enter-leave.po';
import * as auto from './auto.po';
import * as filterStagger from './filter-stagger.po';
import * as heroGroups from './hero-groups';
import {getLinkById, sleepFor} from './util';
import {getComponentSection, getToggleButton} from './querying.po';
describe('Animation Tests', () => {
let driver: webdriver.WebDriver;
const routingAnimationDuration = 350;
const newPageSleepFor = (ms = 0) => sleepFor(ms + routingAnimationDuration);
beforeAll(async () => {
await driver.get('');
});
describe('Open/Close Component', () => {
const closedHeight = '100px';
const openHeight = '200px';
beforeAll(async () => {
await (await getLinkById(driver, 'open-close')).click();
await newPageSleepFor(300);
});
it('should be open', async () => {
const toggleButton = await openClose.getToggleButton(driver);
const container = await openClose.getComponentContainer(driver);
let text = await container.getText();
if (text.includes('Closed')) {
await toggleButton.click();
await driver.wait(async () => (await container.getCssValue('height')) === openHeight, 2000);
}
text = await container.getText();
const containerHeight = await container.getCssValue('height');
expect(text).toContain('The box is now Open!');
expect(containerHeight).toBe(openHeight);
});
it('should be closed', async () => {
const toggleButton = await openClose.getToggleButton(driver);
const container = await openClose.getComponentContainer(driver);
let text = await container.getText();
if (text.includes('Open')) {
await toggleButton.click();
await driver.wait(
async () => (await container.getCssValue('height')) === closedHeight,
2000,
);
}
text = await container.getText();
const containerHeight = await container.getCssValue('height');
expect(text).toContain('The box is now Closed!');
expect(containerHeight).toBe(closedHeight);
});
it('should log animation events', async () => {
const toggleButton = await openClose.getToggleButton(driver);
const loggingCheckbox = await openClose.getLoggingCheckbox(driver);
await loggingCheckbox.click();
await toggleButton.click();
const logs = await driver.manage().logs().get(webdriver.logging.Type.BROWSER);
const animationMessages = logs.filter(({message}) => message.includes('Animation'));
expect(animationMessages.length).toBeGreaterThan(0);
});
});
describe('Status Slider Component', () => {
const activeColor = 'rgba(117, 70, 0, 1)';
const inactiveColor = 'rgba(0, 0, 255, 1)';
beforeAll(async () => {
await (await getLinkById(driver, 'status')).click();
await newPageSleepFor(2000);
});
it('should be inactive with a blue background', async () => {
const toggleButton = await statusSlider.getToggleButton(driver);
const container = await statusSlider.getComponentContainer(driver);
let text = await container.getText();
if (text === 'Active') {
await toggleButton.click();
await driver.wait(
async () => (await container.getCssValue('backgroundColor')) === inactiveColor,
3000,
);
}
text = await container.getText();
const bgColor = await container.getCssValue('backgroundColor');
expect(text).toBe('Inactive');
expect(bgColor).toBe(inactiveColor);
});
it('should be active with an orange background', async () => {
const toggleButton = await statusSlider.getToggleButton(driver);
const container = await statusSlider.getComponentContainer(driver);
let text = await container.getText();
if (text === 'Inactive') {
await toggleButton.click();
await driver.wait(
async () => (await container.getCssValue('backgroundColor')) === activeColor,
3000,
);
}
text = await container.getText();
const bgColor = await container.getCssValue('backgroundColor');
expect(text).toBe('Active');
expect(bgColor).toBe(activeColor);
});
});
describe('Toggle Animations Component', () => {
beforeAll(async () => {
await (await getLinkById(driver, 'toggle')).click();
await newPageSleepFor();
});
it('should disabled animations on the child element', async () => {
const toggleButton = await toggle.getToggleAnimationsButton(driver);
await toggleButton.click();
const container = await toggle.getComponentContainer(driver);
const cssClasses = await container.getAttribute('class');
expect(cssClasses).toContain('ng-animate-disabled');
});
});
describe('Enter/Leave Component', () => {
beforeAll(async () => {
await (await getLinkById(driver, 'enter-leave')).click();
await newPageSleepFor(100);
});
it('should attach a flyInOut trigger to the list of items', async () => {
const heroesList = await enterLeave.getHeroesList(driver);
const hero = heroesList[0];
const cssClasses = await hero.getAttribute('class');
const transform = await hero.getCssValue('transform');
expect(cssClasses).toContain('ng-trigger-flyInOut');
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
});
it('should remove the hero from the list when clicked', async () => {
const heroesList = await enterLeave.getHeroesList(driver);
const total = heroesList.length;
const hero = heroesList[0];
await hero.click();
await driver.wait(async () => (await enterLeave.getHeroesList(driver)).length < total, 2000);
});
});
describe('Auto Calculation Component', () => {
beforeAll(async () => {
await (await getLinkById(driver, 'auto')).click();
await newPageSleepFor();
});
it('should attach a shrinkOut trigger to the list of items', async () => {
const heroesList = await auto.getHeroesList(driver);
const hero = heroesList[0];
const cssClasses = await hero.getAttribute('class');
expect(cssClasses).toContain('ng-trigger-shrinkOut');
});
it('should remove the hero from the list when clicked', async () => {
const heroesList = await auto.getHeroesList(driver);
const total = heroesList.length;
const hero = heroesList[0];
await hero.click();
await driver.wait(async () => (await auto.getHeroesList(driver)).length < total, 2000);
});
});
describe('Filter/Stagger Component', () => {
beforeAll(async () => {
await (await getLinkById(driver, 'heroes')).click();
await newPageSleepFor();
});
it('should attach a filterAnimations trigger to the list container', async () => {
const heroesList = await filterStagger.getComponentContainer(driver);
const cssClasses = await heroesList.getAttribute('class');
expect(cssClasses).toContain('ng-trigger-filterAnimation');
});
it('should filter down the list when a search is performed', async () => {
const heroesList = await filterStagger.getHeroesList(driver);
const total = heroesList.length;
const input = await filterStagger.getInput(driver);
await input.sendKeys('Mag');
await driver.wait(async () => (await filterStagger.getHeroesList(driver)).length === 2, 2000);
const newTotal = (await filterStagger.getHeroesList(driver)).length;
expect(newTotal).toBeLessThan(total);
});
});
describe('Hero Groups Component', () => {
beforeAll(async () => {
await (await getLinkById(driver, 'hero-groups')).click();
await newPageSleepFor(400);
});
it('should attach a flyInOut trigger to the list of items', async () => {
const heroesList = await heroGroups.getHeroesList(driver);
const hero = heroesList[0];
const cssClasses = await hero.getAttribute('class');
const transform = await hero.getCssValue('transform');
const opacity = await hero.getCssValue('opacity');
expect(cssClasses).toContain('ng-trigger-flyInOut');
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
expect(opacity).toBe('1');
});
it('should remove the hero from the list when clicked', async () => {
const heroesList = await heroGroups.getHeroesList(driver);
const total = heroesList.length;
const hero = heroesList[0];
await hero.click();
await driver.wait(async () => (await heroGroups.getHeroesList(driver)).length < total, 2000);
});
});
describe('Querying Component', () => {
const queryingAnimationDuration = 2500;
beforeAll(async () => {
await (await getLinkById(driver, 'querying')).click();
await newPageSleepFor(queryingAnimationDuration);
});
it('should toggle the section', async () => {
const toggleButton = await getToggleButton(driver);
const section = await getComponentSection(driver);
expect(await section.isDisplayed()).toBe(true);
// toggling off
await toggleButton.click();
await newPageSleepFor(queryingAnimationDuration);
const sectionsOff = await driver.findElements(webdriver.By.css('app-querying section'));
expect(sectionsOff.length).toBe(0);
// toggling on
await toggleButton.click();
await newPageSleepFor(queryingAnimationDuration);
const sectionsOn = await driver.findElements(webdriver.By.css('app-querying section'));
expect(sectionsOn.length).toBe(1);
await newPageSleepFor(queryingAnimationDuration);
});
it(`should disable the button for the animation's duration`, async () => {
const toggleButton = await getToggleButton(driver);
expect(await toggleButton.isEnabled()).toBe(true);
// toggling off
await toggleButton.click();
expect(await toggleButton.isEnabled()).toBe(false);
await newPageSleepFor(queryingAnimationDuration);
expect(await toggleButton.isEnabled()).toBe(true);
// toggling on
await toggleButton.click();
expect(await toggleButton.isEnabled()).toBe(false);
await newPageSleepFor(queryingAnimationDuration);
expect(await toggleButton.isEnabled()).toBe(true);
});
});
});
@@ -1,19 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getPage(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-hero-list-auto-page'));
}
export function getComponent(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-hero-list-auto'));
}
export async function getComponentContainer(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-hero-list-auto'), webdriver.By.css('ul'));
}
export async function getHeroesList(driver: webdriver.WebDriver) {
const container = await getComponentContainer(driver);
return container.findElements(webdriver.By.css('li'));
}
@@ -1,19 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getPage(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-hero-list-enter-leave-page'));
}
export function getComponent(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-hero-list-enter-leave'));
}
export async function getComponentContainer(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-hero-list-enter-leave'), webdriver.By.css('ul'));
}
export async function getHeroesList(driver: webdriver.WebDriver) {
const container = await getComponentContainer(driver);
return container.findElements(webdriver.By.css('li'));
}
@@ -1,19 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getPage(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-hero-list-page'));
}
export async function getComponentContainer(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-hero-list-page'), webdriver.By.css('ul'));
}
export async function getHeroesList(driver: webdriver.WebDriver) {
const container = await getComponentContainer(driver);
return container.findElements(webdriver.By.css('li'));
}
export async function getInput(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-hero-list-page'), webdriver.By.css('input'));
}
@@ -1,19 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getPage(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-hero-list-groups-page'));
}
export function getComponent(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-hero-list-groups'));
}
export async function getComponentContainer(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-hero-list-groups'), webdriver.By.css('ul'));
}
export async function getHeroesList(driver: webdriver.WebDriver) {
const container = await getComponentContainer(driver);
return container.findElements(webdriver.By.css('li'));
}
@@ -1,27 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getPage(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-open-close-page'));
}
export function getComponent(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-open-close'));
}
export async function getToggleButton(driver: webdriver.WebDriver) {
const comp = await getComponent(driver);
return comp.findElement(webdriver.By.xpath('.//button[normalize-space()="Toggle Open/Close"]'));
}
export async function getLoggingCheckbox(driver: webdriver.WebDriver) {
return locate(
driver,
webdriver.By.css('app-open-close-page'),
webdriver.By.css('section > input[type="checkbox"]'),
);
}
export async function getComponentContainer(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-open-close'), webdriver.By.css('div'));
}
@@ -1,14 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getComponent(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-querying'));
}
export async function getToggleButton(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-querying'), webdriver.By.className('toggle'));
}
export async function getComponentSection(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-querying'), webdriver.By.css('section'));
}
@@ -1,19 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getPage(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-status-slider-page'));
}
export function getComponent(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-status-slider'));
}
export async function getToggleButton(driver: webdriver.WebDriver) {
const comp = await getComponent(driver);
return comp.findElement(webdriver.By.xpath('.//button[normalize-space()="Toggle Status"]'));
}
export async function getComponentContainer(driver: webdriver.WebDriver) {
return locate(driver, webdriver.By.css('app-status-slider'), webdriver.By.css('div'));
}
@@ -1,26 +0,0 @@
import * as webdriver from 'selenium-webdriver';
import {locate} from './util';
export function getPage(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-toggle-animations-child-page'));
}
export function getComponent(driver: webdriver.WebDriver) {
return driver.findElement(webdriver.By.css('app-open-close-toggle'));
}
export async function getToggleButton(driver: webdriver.WebDriver) {
const comp = await getComponent(driver);
return comp.findElement(webdriver.By.xpath('.//button[normalize-space()="Toggle Open/Closed"]'));
}
export async function getToggleAnimationsButton(driver: webdriver.WebDriver) {
const comp = await getComponent(driver);
return comp.findElement(webdriver.By.xpath('.//button[normalize-space()="Toggle Animations"]'));
}
export async function getComponentContainer(driver: webdriver.WebDriver) {
const comp = await getComponent(driver);
const divs = await comp.findElements(webdriver.By.css('div'));
return divs[0];
}
@@ -1,23 +0,0 @@
import * as webdriver from 'selenium-webdriver';
/**
* locate(parent, finder1, finder2) => parent.findElement(finder1).findElement(finder2);
*/
export async function locate(
parent: webdriver.WebDriver | webdriver.WebElement,
...locators: webdriver.Locator[]
): Promise<webdriver.WebElement> {
let current: webdriver.WebElement = await parent.findElement(locators[0]);
for (let i = 1; i < locators.length; i++) {
current = await current.findElement(locators[i]);
}
return current;
}
export async function sleepFor(time = 1000) {
return new Promise((resolve) => setTimeout(resolve, time));
}
export function getLinkById(driver: webdriver.WebDriver, id: string) {
return driver.findElement(webdriver.By.css(`a[id=${id}]`));
}
@@ -1,37 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Attribute directives', () => {
let driver: webdriver.WebDriver;
const title = 'My First Attribute Directive';
beforeAll(async () => {
await driver.get('');
});
it(`should display correct title: ${title}`, async () => {
expect(await driver.findElement(webdriver.By.css('h1')).getText()).toEqual(title);
});
it('should be able to select green highlight', async () => {
const paragraphs = await driver.findElements(webdriver.By.css('p'));
let highlightedEle: webdriver.WebElement | null = null;
for (const p of paragraphs) {
if ((await p.getText()).includes('Highlight me!')) {
highlightedEle = p;
break;
}
}
const lightGreen = 'rgba(144, 238, 144, 1)';
const getBgColor = () => highlightedEle!.getCssValue('background-color');
expect(await getBgColor()).not.toEqual(lightGreen);
const greenRb = (await driver.findElements(webdriver.By.css('input')))[0];
await greenRb.click();
await driver.actions().move({origin: highlightedEle!}).perform();
// Wait for up to 4s for the background color to be updated,
// to account for slow environments (e.g. CI).
await driver.wait(async () => (await getBgColor()) === lightGreen, 4000);
});
});
@@ -1,85 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Built-in Directives', () => {
let driver: webdriver.WebDriver;
beforeAll(async () => {
await driver.get('');
});
it('should have title Built-in Directives', async () => {
const title = (await driver.findElements(webdriver.By.css('h1')))[0];
expect(await title.getText()).toEqual('Built-in Directives');
});
it('should change first Teapot header', async () => {
const firstLabel = (await driver.findElements(webdriver.By.css('p')))[0];
const firstInput = (await driver.findElements(webdriver.By.css('input')))[0];
expect(await firstLabel.getText()).toEqual('Current item name: Teapot');
await firstInput.sendKeys('abc');
expect(await firstLabel.getText()).toEqual('Current item name: Teapotabc');
});
it('should modify sentence when modified checkbox checked', async () => {
const modifiedChkbxLabel = (
await driver.findElements(webdriver.By.css('input[type="checkbox"]'))
)[1];
const modifiedSentence = (await driver.findElements(webdriver.By.css('div')))[1];
await modifiedChkbxLabel.click();
expect(await modifiedSentence.getText()).toContain('modified');
});
it('should modify sentence when normal checkbox checked', async () => {
const normalChkbxLabel = (
await driver.findElements(webdriver.By.css('input[type="checkbox"]'))
)[4];
const normalSentence = (await driver.findElements(webdriver.By.css('div')))[7];
await normalChkbxLabel.click();
expect(await normalSentence.getText()).toContain('normal weight and, extra large');
});
it('should toggle app-item-detail', async () => {
const toggleButton = (await driver.findElements(webdriver.By.css('button')))[3];
const toggledDiv = (await driver.findElements(webdriver.By.css('app-item-detail')))[0];
await toggleButton.click();
expect(await toggledDiv.isDisplayed()).toBe(true);
});
it('should hide app-item-detail', async () => {
const hiddenMessage = (await driver.findElements(webdriver.By.css('p')))[10];
const hiddenDiv = (await driver.findElements(webdriver.By.css('app-item-detail')))[2];
expect(await hiddenMessage.getText()).toContain('in the DOM');
expect(await hiddenDiv.isDisplayed()).toBe(true);
});
it('should have 10 lists each containing the string Teapot', async () => {
const boxes = await driver.findElements(webdriver.By.css('.box'));
const teapotBoxes: webdriver.WebElement[] = [];
for (const b of boxes) {
if ((await b.getText()).includes('Teapot')) {
teapotBoxes.push(b);
}
}
expect(teapotBoxes.length).toBe(10);
});
it('should switch case', async () => {
const tvRadioButton = (await driver.findElements(webdriver.By.css('input[type="radio"]')))[3];
const tvDiv = driver.findElement(webdriver.By.css('app-lost-item'));
const fishbowlRadioButton = (
await driver.findElements(webdriver.By.css('input[type="radio"]'))
)[4];
const fishbowlDiv = driver.findElement(webdriver.By.css('app-unknown-item'));
await tvRadioButton.click();
expect(await tvDiv.getText()).toContain('Television');
await fishbowlRadioButton.click();
expect(await fishbowlDiv.getText()).toContain('mysterious');
});
});
@@ -1,75 +0,0 @@
button {
font-size: 100%;
margin: 0 2px;
}
div[ng-reflect-ng-switch], app-unknown-item {
margin: .5rem 0;
display: block;
}
#noTrackByCnt,
#withTrackByCnt {
color: darkred;
max-width: 450px;
margin: 4px;
}
img {
height: 100px;
}
.box {
border: 1px solid black;
padding: 6px;
max-width: 450px;
}
.child-div {
margin-left: 1em;
font-weight: normal;
}
.context {
margin-left: 1em;
}
.hidden {
display: none;
}
.parent-div {
margin-top: 1em;
font-weight: bold;
}
.course {
font-weight: bold;
font-size: x-large;
}
.helpful {
color: red;
}
.saveable {
color: limegreen;
}
.study,
.modified {
font-family: "Brush Script MT", cursive;
font-size: 2rem;
}
.toe {
margin-left: 1em;
font-style: italic;
}
.to-toc {
margin-top: 10px;
display: block;
}
@@ -1,262 +0,0 @@
<h1>Built-in Directives</h1>
<h2>Built-in attribute directives</h2>
<h3 id="ngModel">NgModel (two-way) Binding</h3>
<fieldset>
<h4>NgModel examples</h4>
<p>Current item name: {{ currentItem.name }}</p>
<p>
<label for="without">without NgModel:</label>
<input [value]="currentItem.name" (input)="currentItem.name = getValue($event)" id="without" />
</p>
<p>
<!-- #docregion NgModel-1 -->
<label for="example-ngModel">[(ngModel)]:</label>
<input [(ngModel)]="currentItem.name" id="example-ngModel" />
<!-- #enddocregion NgModel-1 -->
</p>
<p>
<label for="example-change">(ngModelChange)="...name=$event":</label>
<input
[ngModel]="currentItem.name"
(ngModelChange)="currentItem.name = $event"
id="example-change"
/>
</p>
<p>
<label for="example-uppercase"
>(ngModelChange)="setUppercaseName($event)"
<!-- #docregion uppercase -->
<input
[ngModel]="currentItem.name"
(ngModelChange)="setUppercaseName($event)"
id="example-uppercase"
/>
<!-- #enddocregion uppercase -->
</label>
</p>
</fieldset>
<hr />
<h2 id="ngClass">NgClass Binding</h2>
<p>currentClasses is {{ currentClasses | json }}</p>
<!-- #docregion NgClass-1 -->
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>
<!-- #enddocregion NgClass-1 -->
<ul>
<li>
<label for="saveable">saveable</label>
<input type="checkbox" [(ngModel)]="canSave" id="saveable" />
</li>
<li>
<label for="modified">modified:</label>
<input
type="checkbox"
[value]="!isUnchanged"
(change)="isUnchanged = !isUnchanged"
id="modified"
/>
</li>
<li>
<label for="special"
>special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"
/></label>
</li>
</ul>
<button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button>
<div [ngClass]="currentClasses">
This div should be {{ canSave ? '' : 'not' }} saveable,
{{ isUnchanged ? 'unchanged' : 'modified' }} and {{ isSpecial ? '' : 'not' }} special after
clicking "Refresh".
</div>
<br /><br />
<!-- #docregion special-div -->
<!-- toggle the "special" class on/off with a property -->
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
<!-- #enddocregion special-div -->
<div class="helpful study course">Helpful study course</div>
<div [ngClass]="{'helpful': false, 'study': true, 'course': true}">Study course</div>
<!-- NgStyle binding -->
<hr />
<h3>NgStyle Binding</h3>
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">This div is x-large or smaller.</div>
<h4>[ngStyle] binding to currentStyles - CSS property names</h4>
<p>currentStyles is {{ currentStyles | json }}</p>
<!-- #docregion NgStyle-2 -->
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
<!-- #enddocregion NgStyle-2 -->
<br />
<label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave" /></label> |
<label for="isUnchanged"
>normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"
/></label>
|
<label for="isSpecial"
>xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"
/></label>
<button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button>
<br /><br />
<div [ngStyle]="currentStyles">
This div should be {{ canSave ? 'italic' : 'plain' }},
{{ isUnchanged ? 'normal weight' : 'bold' }} and,
{{ isSpecial ? 'extra large' : 'normal size' }} after clicking "Refresh".
</div>
<hr />
<h2>Built-in structural directives</h2>
<h3 id="ngIf">NgIf Binding</h3>
<div>
<p>If isActive is true, app-item-detail will render:</p>
<!-- #docregion NgIf-1 -->
<app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>
<!-- #enddocregion NgIf-1 -->
<button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button>
</div>
<p>If currentCustomer isn't null, say hello to Laura:</p>
<!-- #docregion NgIf-2 -->
<div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div>
<!-- #enddocregion NgIf-2 -->
<p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p>
<!-- #docregion NgIf-2b -->
<div *ngIf="nullCustomer">
Hello, <span>{{ nullCustomer }}</span>
</div>
<!-- #enddocregion NgIf-2b -->
<button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button>
<h4>NgIf binding with template (no *)</h4>
<ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template>
<hr />
<h4>Show/hide vs. NgIf</h4>
<!-- isSpecial is true -->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>
<p>ItemDetail is in the DOM but hidden</p>
<app-item-detail [class.hidden]="isSpecial"></app-item-detail>
<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
<hr />
<h2 id="ngFor">NgFor Binding</h2>
<div class="box">
<!-- #docregion NgFor-1, NgFor-1-2 -->
<div *ngFor="let item of items">{{ item.name }}</div>
<!-- #enddocregion NgFor-1, NgFor-1-2 -->
</div>
<p>*ngFor with ItemDetailComponent element</p>
<div class="box">
<!-- #docregion NgFor-2, NgFor-1-2 -->
<app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>
<!-- #enddocregion NgFor-2, NgFor-1-2 -->
</div>
<h4 id="ngFor-index">*ngFor with index</h4>
<p>with <em>semi-colon</em> separator</p>
<div class="box">
<!-- #docregion NgFor-3 -->
<div *ngFor="let item of items; let i = index">{{ i + 1 }} - {{ item.name }}</div>
<!-- #enddocregion NgFor-3 -->
</div>
<p>with <em>comma</em> separator</p>
<div class="box">
<div *ngFor="let item of items; let i = index">{{ i + 1 }} - {{ item.name }}</div>
</div>
<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
<button type="button" (click)="resetList()">Reset items</button>
<button type="button" (click)="changeIds()">Change ids</button>
<button type="button" (click)="clearTrackByCounts()">Clear counts</button>
<p><em>without</em> trackBy</p>
<div class="box">
<div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div>
<div id="noTrackByCnt" *ngIf="itemsNoTrackByCount">
Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy
</div>
</div>
<p>with trackBy</p>
<div class="box">
<div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">
({{ item.id }}) {{ item.name }}
</div>
<div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">
Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy
</div>
</div>
<br /><br /><br />
<p>with trackBy and <em>semi-colon</em> separator</p>
<div class="box">
<!-- #docregion trackBy -->
<div *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>
<!-- #enddocregion trackBy -->
</div>
<p>with trackBy and <em>comma</em> separator</p>
<div class="box">
<div *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>
</div>
<p>with trackBy and <em>space</em> separator</p>
<div class="box">
<div *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div>
</div>
<p>with <em>generic</em> trackById function</p>
<div class="box">
<div *ngFor="let item of items; trackBy: trackById">({{ item.id }}) {{ item.name }}</div>
</div>
<hr />
<h2>NgSwitch Binding</h2>
<p>Pick your favorite item</p>
<div>
<label for="item-{{ i }}" *ngFor="let i of items">
<div>
<input id="item-{{ i }}" type="radio" name="items" [(ngModel)]="currentItem" [value]="i" />{{
i.name
}}
</div>
</label>
</div>
<!-- #docregion NgSwitch -->
<div [ngSwitch]="currentItem.feature">
<app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item>
<app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item>
<app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item>
<app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item>
<!-- #enddocregion NgSwitch -->
<!-- #docregion NgSwitch-div -->
<div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div>
<!-- #enddocregion NgSwitch-div -->
<!-- #docregion NgSwitch -->
<app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item>
</div>
<!-- #enddocregion NgSwitch -->
@@ -1,165 +0,0 @@
import {Component, OnInit} from '@angular/core';
import {JsonPipe, NgClass, NgStyle} from '@angular/common';
// #docregion import-ng-if
import {NgIf} from '@angular/common';
// #enddocregion import-ng-if
// #docregion import-ng-for
import {NgFor} from '@angular/common';
// #enddocregion import-ng-for
// #docregion import-ng-switch
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
// #enddocregion import-ng-switch
// #docregion import-forms-module
import {FormsModule} from '@angular/forms';
// #enddocregion import-forms-module
import {Item} from './item';
import {ItemDetailComponent} from './item-detail/item-detail.component';
import {ItemSwitchComponents} from './item-switch.component';
import {StoutItemComponent} from './item-switch.component';
// #docregion import-ng-if, import-ng-for, import-ng-switch, import-forms-module
@Component({
// #enddocregion import-ng-if, import-ng-for, import-ng-switch, import-forms-module
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
imports: [
NgClass,
NgStyle,
// #docregion import-ng-if
NgIf, // <-- import into the component
// #enddocregion import-ng-if
// #docregion import-ng-for
NgFor, // <-- import into the component
// #enddocregion import-ng-for
// #docregion import-ng-switch
NgSwitch, // <-- import into the component
NgSwitchCase,
NgSwitchDefault,
// #enddocregion import-ng-switch
// #docregion import-forms-module
FormsModule, // <--- import into the component
// #enddocregion import-forms-module
JsonPipe,
ItemDetailComponent,
ItemSwitchComponents,
StoutItemComponent,
// #docregion import-ng-if, import-ng-for, import-ng-switch, import-forms-module
],
})
export class AppComponent implements OnInit {
// #enddocregion import-ng-if, import-ng-for, import-ng-switch, import-forms-module
canSave = true;
isSpecial = true;
isUnchanged = true;
isActive = true;
nullCustomer: string | null = null;
currentCustomer = {
name: 'Laura',
};
item!: Item; // defined to demonstrate template context precedence
items: Item[] = [];
// #docregion item
currentItem!: Item;
// #enddocregion item
// trackBy change counting
itemsNoTrackByCount = 0;
itemsWithTrackByCount = 0;
itemsWithTrackByCountReset = 0;
itemIdIncrement = 1;
// #docregion setClasses
currentClasses: Record<string, boolean> = {};
// #enddocregion setClasses
// #docregion setStyles
currentStyles: Record<string, string> = {};
// #enddocregion setStyles
ngOnInit() {
this.resetItems();
this.setCurrentClasses();
this.setCurrentStyles();
this.itemsNoTrackByCount = 0;
}
setUppercaseName(name: string) {
this.currentItem.name = name.toUpperCase();
}
// #docregion setClasses
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
saveable: this.canSave,
modified: !this.isUnchanged,
special: this.isSpecial,
};
}
// #enddocregion setClasses
// #docregion setStyles
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px',
};
}
// #enddocregion setStyles
isActiveToggle() {
this.isActive = !this.isActive;
}
giveNullCustomerValue() {
this.nullCustomer = 'Kelly';
}
resetItems() {
this.items = Item.items.map((item) => item.clone());
this.currentItem = this.items[0];
this.item = this.currentItem;
}
resetList() {
this.resetItems();
this.itemsWithTrackByCountReset = 0;
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
}
changeIds() {
this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement));
this.itemsWithTrackByCountReset = -1;
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;
}
clearTrackByCounts() {
this.resetItems();
this.itemsNoTrackByCount = 0;
this.itemsWithTrackByCount = 0;
this.itemIdIncrement = 1;
}
// #docregion trackByItems
trackByItems(index: number, item: Item): number {
return item.id;
}
// #enddocregion trackByItems
trackById(index: number, item: any): number {
return item.id;
}
getValue(event: Event): string {
return (event.target as HTMLInputElement).value;
}
// #docregion import-ng-if, import-ng-for, import-ng-switch, import-forms-module
}
// #enddocregion import-ng-if, import-ng-for, import-ng-switch, import-forms-module
@@ -1,3 +0,0 @@
<div>
<span>{{ item()?.name }}</span>
</div>
@@ -1,12 +0,0 @@
import {Component, input} from '@angular/core';
import {Item} from '../item';
@Component({
selector: 'app-item-detail',
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.css'],
})
export class ItemDetailComponent {
readonly item = input<Item | undefined>();
}
@@ -1,57 +0,0 @@
import {Component, computed, input} from '@angular/core';
import {Item} from './item';
@Component({
selector: 'app-stout-item',
template: "I'm a little {{item().name}}, short and stout!",
})
// #docregion input
export class StoutItemComponent {
item = input.required<Item>();
}
// #enddocregion input
@Component({
selector: 'app-best-item',
template: 'This is the brightest {{item().name}} in town.',
})
export class BestItemComponent {
item = input.required<Item>();
}
@Component({
selector: 'app-device-item',
template: 'Which is the slimmest {{item().name}}?',
})
export class DeviceItemComponent {
item = input.required<Item>();
}
@Component({
selector: 'app-lost-item',
template: 'Has anyone seen my {{item().name}}?',
})
export class LostItemComponent {
item = input.required<Item>();
}
@Component({
selector: 'app-unknown-item',
template: '{{message()}}',
})
export class UnknownItemComponent {
readonly item = input<Item | undefined>(undefined);
readonly message = computed(() => {
const itemName = this.item()?.name;
return itemName ? `${itemName} is strange and mysterious.` : 'A mystery wrapped in a fishbowl.';
});
}
export const ItemSwitchComponents = [
StoutItemComponent,
BestItemComponent,
DeviceItemComponent,
LostItemComponent,
UnknownItemComponent,
];
@@ -1,25 +0,0 @@
export class Item {
static nextId = 0;
static items: Item[] = [
new Item(0, 'Teapot', 'stout'),
new Item(1, 'Lamp', 'bright'),
new Item(2, 'Phone', 'slim'),
new Item(3, 'Television', 'vintage'),
new Item(4, 'Fishbowl'),
];
constructor(
public id: number,
public name?: string,
public feature?: string,
public url?: string,
public rate = 100,
) {
this.id = id ? id : Item.nextId++;
}
clone(): Item {
return Object.assign(new Item(this.id), this);
}
}
@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>BuiltInDirectives</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>
@@ -1,5 +0,0 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
bootstrapApplication(AppComponent).catch((err) => console.error(err));
@@ -1,10 +0,0 @@
{
"description": "Built-in Directives",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Built-in Directives"]
}
@@ -1,34 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Dynamic Form', () => {
let driver: webdriver.WebDriver;
beforeAll(async () => {
await driver.get('');
});
it('should submit form', async () => {
const firstNameElement = (
await driver.findElements(webdriver.By.css('input[id=firstName]'))
)[0];
expect(await firstNameElement.getAttribute('value')).toEqual('Bombasto');
const emailElement = (await driver.findElements(webdriver.By.css('input[id=emailAddress]')))[0];
const email = 'test@test.com';
await emailElement.sendKeys(email);
expect(await emailElement.getAttribute('value')).toEqual(email);
await (await driver.findElement(webdriver.By.css('select option[value="solid"]'))).click();
await (await driver.findElements(webdriver.By.css('button')))[0].click();
const strongs = await driver.findElements(webdriver.By.css('strong'));
let found = false;
for (const s of strongs) {
if ((await s.getText()).includes('Saved the following values')) {
found = true;
break;
}
}
expect(found).toBe(true);
});
});
@@ -1,97 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Elements', () => {
let driver: webdriver.WebDriver;
const click = async (elem: webdriver.WebElement) => {
await driver.wait(webdriver.until.elementIsVisible(elem), 5000);
await elem.click();
};
const waitForText = async (elem: webdriver.WebElement) => {
await driver.wait(async () => /\S/.test(await elem.getText()), 5000);
};
const isPresent = async (locator: webdriver.Locator) => {
const els = await driver.findElements(locator);
return els.length > 0;
};
beforeEach(async () => {
await driver.get('');
});
describe('popup component', () => {
it('should be displayed on button click', async () => {
const popupComponentLocator = webdriver.By.css('popup-component');
expect(await isPresent(popupComponentLocator)).toBe(false);
const popupButtons = await driver.findElements(webdriver.By.css('button'));
await click(popupButtons[0]);
expect(await isPresent(popupComponentLocator)).toBe(true);
});
it('should display the specified message', async () => {
const messageInput = await driver.findElement(webdriver.By.css('input'));
await messageInput.clear();
await messageInput.sendKeys('Angular rocks!');
const popupButtons = await driver.findElements(webdriver.By.css('button'));
await click(popupButtons[0]);
const popupComponent = await driver.findElement(webdriver.By.css('popup-component'));
await waitForText(popupComponent);
expect(await popupComponent.getText()).toContain('Popup: Angular rocks!');
});
it('should be closed on "close" button click', async () => {
const popupComponentLocator = webdriver.By.css('popup-component');
const popupButtons = await driver.findElements(webdriver.By.css('button'));
await click(popupButtons[0]);
expect(await isPresent(popupComponentLocator)).toBe(true);
const popupComponent = await driver.findElement(popupComponentLocator);
const closeButton = await popupComponent.findElement(webdriver.By.css('button'));
await click(closeButton);
expect(await isPresent(popupComponentLocator)).toBe(false);
});
});
describe('popup element', () => {
it('should be displayed on button click', async () => {
const popupElementLocator = webdriver.By.css('popup-element');
expect(await isPresent(popupElementLocator)).toBe(false);
const popupButtons = await driver.findElements(webdriver.By.css('button'));
await click(popupButtons[1]);
expect(await isPresent(popupElementLocator)).toBe(true);
});
it('should display the specified message', async () => {
const messageInput = await driver.findElement(webdriver.By.css('input'));
await messageInput.clear();
await messageInput.sendKeys('Angular rocks!');
const popupButtons = await driver.findElements(webdriver.By.css('button'));
await click(popupButtons[1]);
const popupElement = await driver.findElement(webdriver.By.css('popup-element'));
await waitForText(popupElement);
expect(await popupElement.getText()).toContain('Popup: Angular rocks!');
});
it('should be closed on "close" button click', async () => {
const popupElementLocator = webdriver.By.css('popup-element');
const popupButtons = await driver.findElements(webdriver.By.css('button'));
await click(popupButtons[1]);
expect(await isPresent(popupElementLocator)).toBe(true);
const popupElement = await driver.findElement(popupElementLocator);
const closeButton = await popupElement.findElement(webdriver.By.css('button'));
await click(closeButton);
expect(await isPresent(popupElementLocator)).toBe(false);
});
});
});
@@ -1,249 +0,0 @@
import * as webdriver from 'selenium-webdriver';
// THESE TESTS ARE INCOMPLETE
describe('Form Validation Tests', () => {
let driver: webdriver.WebDriver;
beforeAll(async () => {
await driver.get('');
});
describe('Template-driven form', () => {
beforeAll(() => {
getPage('app-actor-form-template');
});
tests('Template-Driven Form');
bobTests();
asyncValidationTests();
crossValidationTests();
});
describe('Reactive form', () => {
beforeAll(() => {
getPage('app-actor-form-reactive');
});
tests('Reactive Form');
bobTests();
asyncValidationTests();
crossValidationTests();
});
const testName = 'Test Name';
let page: {
sectionTag: string;
section: () => webdriver.WebElement;
form: () => webdriver.WebElement;
title: () => webdriver.WebElement;
nameInput: () => webdriver.WebElement;
roleInput: () => webdriver.WebElement;
skillSelect: () => webdriver.WebElement;
skillOption: () => webdriver.WebElement;
errorMessages: () => Promise<webdriver.WebElement[]>;
actorFormButtons: () => Promise<webdriver.WebElement[]>;
actorSubmitted: () => webdriver.WebElement;
roleErrors: () => webdriver.WebElement;
crossValidationErrorMessage: () => webdriver.WebElement;
};
function getPage(sectionTag: string) {
const sec = () => driver.findElement(webdriver.By.css(sectionTag));
page = {
sectionTag,
section: sec,
form: () => sec().findElement(webdriver.By.css('form')),
title: () => sec().findElement(webdriver.By.css('h2')),
nameInput: () => sec().findElement(webdriver.By.css('#name')),
roleInput: () => sec().findElement(webdriver.By.css('#role')),
skillSelect: () => sec().findElement(webdriver.By.css('#skill')),
skillOption: () => sec().findElement(webdriver.By.css('#skill option')),
errorMessages: () => sec().findElements(webdriver.By.css('div.alert')),
actorFormButtons: () => sec().findElements(webdriver.By.css('button')),
actorSubmitted: () => sec().findElement(webdriver.By.css('.submitted-message')),
roleErrors: () => sec().findElement(webdriver.By.css('.role-errors')),
crossValidationErrorMessage: () =>
sec().findElement(webdriver.By.css('.cross-validation-error-message')),
};
}
function tests(title: string) {
it('should display correct title', async () => {
expect(await page.title().getText()).toContain(title);
});
it('should not display submitted message before submit', async () => {
const p = await page.actorSubmitted().findElements(webdriver.By.css('p'));
expect(p.length).toBe(0);
});
it('should have form buttons', async () => {
expect((await page.actorFormButtons()).length).toEqual(2);
});
it('should have error at start', async () => {
await expectFormIsInvalid();
});
it('should have disabled submit button', async () => {
expect(await (await page.actorFormButtons())[0].isEnabled()).toBe(false);
});
it('resetting name to valid name should clear errors', async () => {
const ele = page.nameInput();
expect(await ele.isDisplayed()).toBe(true, 'nameInput should exist');
await ele.clear();
await ele.sendKeys(testName);
await expectFormIsValid();
});
it('should produce "required" error after clearing name', async () => {
await page.nameInput().clear();
await page.nameInput().sendKeys('x', webdriver.Key.BACK_SPACE);
expect(await page.form().getAttribute('class')).toMatch('ng-invalid');
expect(await (await page.errorMessages())[0].getText()).toContain('required');
});
it('should produce "at least 4 characters" error when name="x"', async () => {
await page.nameInput().clear();
await page.nameInput().sendKeys('x');
await expectFormIsInvalid();
expect(await (await page.errorMessages())[0].getText()).toContain('at least 4 characters');
});
it('resetting name to valid name again should clear errors', async () => {
await page.nameInput().sendKeys(testName);
await expectFormIsValid();
});
it('should have enabled submit button', async () => {
const submitBtn = (await page.actorFormButtons())[0];
expect(await submitBtn.isEnabled()).toBe(true);
});
it('should hide form after submit', async () => {
await (await page.actorFormButtons())[0].click();
const forms = await driver.findElements(webdriver.By.css(`${page.sectionTag} form`));
expect(forms.length === 0 || !(await forms[0].isDisplayed())).toBe(true);
});
it('submitted form should be displayed', async () => {
const p = await page.actorSubmitted().findElements(webdriver.By.css('p'));
expect(p.length).toBeGreaterThan(0);
});
it('submitted form should have new actor name', async () => {
expect(await page.actorSubmitted().getText()).toContain(testName);
});
it('clicking edit button should reveal form again', async () => {
const newFormBtn = page.actorSubmitted().findElement(webdriver.By.css('button'));
await newFormBtn.click();
const p = await page.actorSubmitted().findElements(webdriver.By.css('p'));
expect(p.length).toBe(0, 'submitted hidden again');
expect(await page.title().isDisplayed()).toBe(true, 'can see form title');
});
}
async function expectFormIsValid() {
expect(await page.form().getAttribute('class')).toMatch('ng-valid');
}
async function expectFormIsInvalid() {
expect(await page.form().getAttribute('class')).toMatch('ng-invalid');
}
async function triggerRoleValidation() {
await driver.findElement(webdriver.By.css('app-root')).click();
}
async function waitForAlterEgoValidation() {
await new Promise((resolve) => setTimeout(resolve, 400));
}
function bobTests() {
const emsg = 'Name cannot be Bob.';
it('should produce "no bob" error after setting name to "Bobby"', async () => {
await page.skillSelect().click();
await page.skillOption().click();
await page.nameInput().clear();
await page.nameInput().sendKeys('Bobby');
await expectFormIsInvalid();
expect(await (await page.errorMessages())[0].getText()).toBe(emsg);
});
it('should be ok again with valid name', async () => {
await page.nameInput().clear();
await page.nameInput().sendKeys(testName);
await expectFormIsValid();
});
}
function asyncValidationTests() {
const emsg = 'Role is already taken.';
it(`should produce "${emsg}" error after setting role to Eric`, async () => {
await page.roleInput().clear();
await page.roleInput().sendKeys('Eric');
await triggerRoleValidation();
await waitForAlterEgoValidation();
await expectFormIsInvalid();
expect(await page.roleErrors().getText()).toBe(emsg);
});
it('should be ok again with different values', async () => {
await page.roleInput().clear();
await page.roleInput().sendKeys('John');
await triggerRoleValidation();
await waitForAlterEgoValidation();
await expectFormIsValid();
const roleErrors = await driver.findElements(
webdriver.By.css(`${page.sectionTag} .role-errors`),
);
expect(roleErrors.length).toBe(0);
});
}
function crossValidationTests() {
const emsg = 'Name cannot match role.';
it(`should produce "${emsg}" error after setting name and role to the same value`, async () => {
await page.nameInput().clear();
await page.nameInput().sendKeys('Romeo');
await page.roleInput().clear();
await page.roleInput().sendKeys('Romeo');
await triggerRoleValidation();
await waitForAlterEgoValidation();
await expectFormIsInvalid();
expect(await page.crossValidationErrorMessage().getText()).toBe(emsg);
});
it('should be ok again with different values', async () => {
await page.nameInput().clear();
await page.nameInput().sendKeys('Romeo');
await page.roleInput().clear();
await page.roleInput().sendKeys('Juliet');
await triggerRoleValidation();
await waitForAlterEgoValidation();
await expectFormIsValid();
const crossErrors = await driver.findElements(
webdriver.By.css(`${page.sectionTag} .cross-validation-error-message`),
);
expect(crossErrors.length).toBe(0);
});
}
});
@@ -1,15 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('forms-overview App', () => {
let driver: webdriver.WebDriver;
beforeEach(async () => {
await driver.get('');
});
it('should display a title', async () => {
expect(await driver.findElement(webdriver.By.css('h1, h2')).getText()).toEqual(
'Forms Overview',
);
});
});
@@ -1,71 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Forms Tests', () => {
let driver: webdriver.WebDriver;
beforeEach(async () => {
await driver.get('');
});
it('should display correct title', async () => {
const h1s = await driver.findElements(webdriver.By.css('h1'));
expect(await h1s[0].getText()).toEqual('Actor Form');
});
it('should not display message before submit', async () => {
const h2s = await driver.findElements(webdriver.By.css('h2'));
expect(h2s.length === 0 || !(await h2s[0].isDisplayed())).toBe(true);
});
it('should hide form after submit', async () => {
const ele = (await driver.findElements(webdriver.By.css('h1')))[0];
expect(await ele.isDisplayed()).toBe(true);
const b = (await driver.findElements(webdriver.By.css('button[type=submit]')))[0];
await b.click();
const h1s = await driver.findElements(webdriver.By.css('h1'));
expect(h1s.length === 0 || !(await h1s[0].isDisplayed())).toBe(true);
});
it('should display message after submit', async () => {
const b = (await driver.findElements(webdriver.By.css('button[type=submit]')))[0];
await b.click();
expect(await (await driver.findElement(webdriver.By.css('h2'))).getText()).toContain(
'You submitted the following',
);
});
it('should hide form after submit', async () => {
const studioEle = (await driver.findElements(webdriver.By.css('input[name=studio]')))[0];
expect(await studioEle.isDisplayed()).toBe(true);
const submitButtonEle = (await driver.findElements(webdriver.By.css('button[type=submit]')))[0];
await submitButtonEle.click();
const studioEles = await driver.findElements(webdriver.By.css('input[name=studio]'));
expect(studioEles.length === 0 || !(await studioEles[0].isDisplayed())).toBe(true);
});
it('should reflect submitted data after submit', async () => {
const studioEle = (await driver.findElements(webdriver.By.css('input[name=studio]')))[0];
const value = await studioEle.getAttribute('value');
const test = 'testing 1 2 3';
const newValue = value + test;
await studioEle.sendKeys(test);
expect(await studioEle.getAttribute('value')).toEqual(newValue);
const b = (await driver.findElements(webdriver.By.css('button[type=submit]')))[0];
await b.click();
const divs = await driver.findElements(webdriver.By.css('div'));
let foundStudio = false;
let foundNewValue = false;
for (const d of divs) {
const text = await d.getText();
if (text.includes('Studio')) foundStudio = true;
if (text.includes(newValue)) foundNewValue = true;
}
expect(foundStudio).toBe(true, 'cannot locate "Studio" label');
expect(foundNewValue).toBe(true, `cannot locate div with this text: ${newValue}`);
});
});
@@ -1,51 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('i18n E2E Tests', () => {
let driver: webdriver.WebDriver;
beforeEach(async () => {
await driver.get('');
});
it('should display i18n translated welcome: Bonjour !', async () => {
expect(await (await driver.findElement(webdriver.By.css('h1'))).getText()).toEqual(
'Bonjour i18n !',
);
});
it('should display the node texts without elements', async () => {
expect(await (await driver.findElement(webdriver.By.css('app-root'))).getText()).toContain(
`Je n'affiche aucun élément`,
);
});
it('should display the translated title attribute', async () => {
const title = await (await driver.findElement(webdriver.By.css('img'))).getAttribute('title');
expect(title).toBe(`Logo d'Angular`);
});
it('should display the ICU plural expression', async () => {
const spans = await driver.findElements(webdriver.By.css('span'));
expect(await spans[0].getText()).toBe(`Mis à jour à l'instant`);
});
it('should display the ICU select expression', async () => {
const spans = await driver.findElements(webdriver.By.css('span'));
const buttons = await driver.findElements(webdriver.By.css('button'));
expect(await spans[1].getText()).toBe(`L'auteur est une femme`);
await buttons[2].click();
expect(await spans[1].getText()).toBe(`L'auteur est un homme`);
});
it('should display the nested expression', async () => {
const spans = await driver.findElements(webdriver.By.css('span'));
const buttons = await driver.findElements(webdriver.By.css('button'));
expect(await spans[2].getText()).toBe(`Mis à jour: à l'instant`);
await buttons[0].click();
expect(await spans[2].getText()).toBe(`Mis à jour: il y a une minute`);
await buttons[0].click();
await buttons[0].click();
await buttons[4].click();
expect(await spans[2].getText()).toBe(`Mis à jour: il y a 3 minutes par autre`);
});
});
@@ -1,181 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Reactive forms', () => {
let driver: webdriver.WebDriver;
const getNameEditor = () => driver.findElement(webdriver.By.css('app-name-editor'));
const getProfileEditor = () => driver.findElement(webdriver.By.css('app-profile-editor'));
const findNavButton = async (text: string) => {
const buttons = await driver.findElements(webdriver.By.css('app-root > nav > button'));
for (const b of buttons) {
if ((await b.getText()).includes(text)) {
return b;
}
}
throw new Error(`Nav button "${text}" not found`);
};
beforeAll(async () => {
await driver.get('');
});
describe('Name Editor', () => {
const nameText = 'John Smith';
beforeAll(async () => {
await (await findNavButton('Name Editor')).click();
});
beforeEach(async () => {
const nameInput = await (await getNameEditor()).findElement(webdriver.By.css('input'));
await nameInput.clear();
});
it('should update the name value when the name control is updated', async () => {
const nameInput = await (await getNameEditor()).findElement(webdriver.By.css('input'));
await nameInput.sendKeys(nameText);
const value = await nameInput.getAttribute('value');
expect(value).toBe(nameText);
});
it('should update the name control when the Update Name button is clicked', async () => {
const nameInput = await (await getNameEditor()).findElement(webdriver.By.css('input'));
const updateButton = await (
await getNameEditor()
).findElement(webdriver.By.xpath('.//button[normalize-space()="Update Name"]'));
await nameInput.sendKeys(nameText);
const value1 = await nameInput.getAttribute('value');
expect(value1).toBe(nameText);
await updateButton.click();
const value2 = await nameInput.getAttribute('value');
expect(value2).toBe('Nancy');
});
it('should update the displayed control value when the name control updated', async () => {
const nameInput = await (await getNameEditor()).findElement(webdriver.By.css('input'));
await nameInput.sendKeys(nameText);
const paragraphs = await (await getNameEditor()).findElements(webdriver.By.css('p'));
let valueElement: webdriver.WebElement | null = null;
for (const p of paragraphs) {
if ((await p.getText()).includes('Value:')) {
valueElement = p;
break;
}
}
const nameValueElement = await valueElement!.getText();
const nameValue = nameValueElement.toString().replace('Value: ', '');
expect(nameValue).toBe(nameText);
});
});
describe('Profile Editor', () => {
const profile: Record<string, string | number> = {
firstName: 'John',
lastName: 'Smith',
street: '345 South Lane',
city: 'Northtown',
state: 'XX',
zip: 12345,
};
beforeAll(async () => {
await (await findNavButton('Profile Editor')).click();
});
beforeEach(async () => {
await driver.get('');
await (await findNavButton('Profile Editor')).click();
});
it('should be invalid by default', async () => {
expect(await (await getProfileEditor()).getText()).toContain('Form Status: INVALID');
});
it('should be valid if the First Name is filled in', async () => {
const firstNameInput = await getInput('firstName');
await firstNameInput.clear();
await firstNameInput.sendKeys('John Smith');
expect(await (await getProfileEditor()).getText()).toContain('Form Status: VALID');
});
it('should update the name when the button is clicked', async () => {
const firstNameInput = await getInput('firstName');
const streetInput = await getInput('street');
const updateButton = await (
await getProfileEditor()
).findElement(webdriver.By.xpath('.//button[normalize-space()="Update Profile"]'));
await firstNameInput.clear();
await streetInput.clear();
await firstNameInput.sendKeys('John');
await streetInput.sendKeys('345 Smith Lane');
const firstNameInitial = await firstNameInput.getAttribute('value');
const streetNameInitial = await streetInput.getAttribute('value');
expect(firstNameInitial).toBe('John');
expect(streetNameInitial).toBe('345 Smith Lane');
await updateButton.click();
const nameValue = await firstNameInput.getAttribute('value');
const streetValue = await streetInput.getAttribute('value');
expect(nameValue).toBe('Nancy');
expect(streetValue).toBe('123 Drew Street');
});
it('should add an alias field when the Add Alias button is clicked', async () => {
const addAliasButton = await driver.findElement(
webdriver.By.xpath('.//button[normalize-space()="+ Add another alias"]'),
);
await addAliasButton.click();
const labels = await (await getProfileEditor()).findElements(webdriver.By.css('label'));
const aliasLabels: webdriver.WebElement[] = [];
for (const l of labels) {
if ((await l.getText()).includes('Alias')) {
aliasLabels.push(l);
}
}
expect(aliasLabels.length).toBe(2);
});
it('should update the displayed form value when form inputs are updated', async () => {
const aliasText = 'Johnny';
for (const key of Object.keys(profile)) {
await (await getInput(key)).sendKeys(`${profile[key]}`);
}
const aliasInput = (
await (await getProfileEditor()).findElements(webdriver.By.css('#alias-0'))
)[0];
await aliasInput.sendKeys(aliasText);
const paragraphs = await (await getProfileEditor()).findElements(webdriver.By.css('p'));
let formValueElement: webdriver.WebElement | null = null;
for (const p of paragraphs) {
if ((await p.getText()).includes('Form Value:')) {
formValueElement = p;
break;
}
}
const formValue = await formValueElement!.getText();
const formJson = JSON.parse(formValue.toString().replace('Form Value:', ''));
expect(profile['firstName']).toBe(formJson.firstName);
expect(profile['lastName']).toBe(formJson.lastName);
expect(formJson.aliases[0]).toBe(aliasText);
});
});
function getInput(key: string) {
return driver.findElement(webdriver.By.css(`input[formcontrolname=${key}]`));
}
});
@@ -1,15 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Routing with Custom Matching', () => {
let driver: webdriver.WebDriver;
beforeAll(async () => {
await driver.get('');
});
it('should display Routing with Custom Matching', async () => {
expect(await (await driver.findElement(webdriver.By.css('h2'))).getText()).toEqual(
'Routing with Custom Matching',
);
});
});
@@ -1,43 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('sw-example App', () => {
let driver: webdriver.WebDriver;
beforeEach(async () => {
await driver.get('');
});
it('should display welcome message', async () => {
expect(await (await driver.findElement(webdriver.By.css('h1, app-root h1'))).getText()).toEqual(
'Welcome to Service Workers!',
);
});
it('should display the Angular logo', async () => {
const imgs = await driver.findElements(webdriver.By.css('img'));
expect(imgs.length).toBeGreaterThan(0);
});
it('should show a header for the list of links', async () => {
const listHeader = await driver.findElement(webdriver.By.css('app-root > h2'));
expect(await listHeader.getText()).toEqual('Here are some links to help you start:');
});
it('should show a list of links', async () => {
const items = await driver.findElements(webdriver.By.css('ul > li > h2 > a'));
expect(items.length).toBe(4);
expect(await items[0].getText()).toBe('Angular Service Worker Intro');
expect(await items[1].getText()).toBe('Tour of Heroes');
expect(await items[2].getText()).toBe('CLI Documentation');
expect(await items[3].getText()).toBe('Angular blog');
});
// Check for a rejected promise as the service worker is not enabled
it('SwUpdate.checkForUpdate() should return a rejected promise', async () => {
const button = await driver.findElement(webdriver.By.css('button'));
const rejectMessage = await driver.findElement(webdriver.By.css('p'));
await button.click();
expect(await rejectMessage.getText()).toContain('rejected: ');
});
});
@@ -1,73 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Structural Directives', () => {
let driver: webdriver.WebDriver;
beforeAll(async () => {
await driver.get('');
});
it('first div should show hero name with *ngIf', async () => {
const allDivs = await driver.findElements(webdriver.By.css('div'));
expect(await allDivs[0].getText()).toEqual('Dr. Nice');
});
it('first li should show hero name with *ngFor', async () => {
const allLis = await driver.findElements(webdriver.By.css('li'));
expect(await allLis[0].getText()).toEqual('Dr. Nice');
});
it('ngSwitch have two <happy-hero> instances', async () => {
const happyHeroEls = await driver.findElements(webdriver.By.css('app-happy-hero'));
expect(happyHeroEls.length).toEqual(2);
});
it('should toggle *ngIf="hero" with a button', async () => {
const buttons = await driver.findElements(webdriver.By.css('button'));
let toggleHeroButton: webdriver.WebElement | null = null;
for (const b of buttons) {
if ((await b.getText()).includes('Toggle hero')) {
toggleHeroButton = b;
break;
}
}
const paragraphs = await driver.findElements(webdriver.By.css('p'));
let paragraph: webdriver.WebElement | null = null;
for (const p of paragraphs) {
if ((await p.getText()).includes('I turned the corner')) {
paragraph = p;
break;
}
}
expect(await paragraph!.getText()).toContain('I waved');
await toggleHeroButton!.click();
expect(await paragraph!.getText()).not.toContain('I waved');
});
it('appUnless should show 3 paragraph (A)s and (B)s at the start', async () => {
const paragraph = await driver.findElements(webdriver.By.css('p.unless'));
expect(paragraph.length).toEqual(3);
for (let i = 0; i < 3; i++) {
expect(await paragraph[i].getText()).toContain('(A)');
}
});
it('appUnless should show 1 paragraph (B) after toggling condition', async () => {
const buttons = await driver.findElements(webdriver.By.css('button'));
let toggleConditionButton: webdriver.WebElement | null = null;
for (const b of buttons) {
if ((await b.getText()).includes('Toggle condition')) {
toggleConditionButton = b;
break;
}
}
await toggleConditionButton!.click();
const paragraph = await driver.findElements(webdriver.By.css('p.unless'));
expect(paragraph.length).toEqual(1);
expect(await paragraph[0].getText()).toContain('(B)');
});
});
@@ -1,71 +0,0 @@
/* #docregion */
button {
min-width: 100px;
font-size: 100%;
}
.box {
border: 1px solid gray;
max-width: 600px;
padding: 4px;
}
.choices {
font-style: italic;
}
code, .code {
background-color: #eee;
color: black;
font-family: Courier, sans-serif;
font-size: 85%;
}
div.code {
width: 400px;
}
.heroic {
font-size: 150%;
font-weight: bold;
}
hr {
margin: 40px 0;
}
.odd {
background-color: palegoldenrod;
}
td, th {
text-align: left;
vertical-align: top;
}
p span {
color: red;
font-size: 70%;
}
.unless {
border: 2px solid;
padding: 6px;
}
p.unless {
width: 500px;
}
button.a, span.a, .unless.a {
color: red;
border-color: gold;
background-color: yellow;
font-size: 100%;
}
button.b, span.b, .unless.b {
color: black;
border-color: green;
background-color: lightgreen;
font-size: 100%;
}
@@ -1,187 +0,0 @@
<!-- #docplaster -->
<!-- #docregion -->
<h1>Structural Directives</h1>
<p>Conditional display of hero</p>
<blockquote>
<!-- #docregion asterisk -->
<div *ngIf="hero" class="name">{{ hero.name }}</div>
<!-- #enddocregion asterisk -->
</blockquote>
<p>List of heroes</p>
<ul>
<li *ngFor="let hero of heroes">{{ hero.name }}</li>
</ul>
<hr />
<h2 id="ngIf">NgIf</h2>
<p *ngIf="true">Expression is true and ngIf is true. This paragraph is in the DOM.</p>
<p *ngIf="false">Expression is false and ngIf is false. This paragraph is not in the DOM.</p>
<p [style.display]="'block'">Expression sets display to "block". This paragraph is visible.</p>
<p [style.display]="'none'">
Expression sets display to "none". This paragraph is hidden but still in the DOM.
</p>
<h4>NgIf with template</h4>
<p>&lt;ng-template&gt; element</p>
<!-- #docregion ngif-template -->
<ng-template [ngIf]="hero">
<div class="name">{{ hero.name }}</div>
</ng-template>
<!-- #enddocregion ngif-template -->
<hr />
<h2 id="ng-container">&lt;ng-container&gt;</h2>
<h4>*ngIf with a &lt;ng-container&gt;</h4>
<button type="button" (click)="hero = hero ? null : heroes[0]">Toggle hero</button>
<!-- #docregion ngif-ngcontainer -->
<p>
I turned the corner
<ng-container *ngIf="hero"> and saw {{ hero.name }}. I waved </ng-container>
and continued on my way.
</p>
<!-- #enddocregion ngif-ngcontainer -->
<p>
I turned the corner
<span *ngIf="hero"> and saw {{ hero.name }}. I waved </span>
and continued on my way.
</p>
<p><em>&lt;select&gt; with &lt;span&gt;</em></p>
<div>
Pick your favorite hero (<label for="show-sad"
><input id="show-sad" type="checkbox" checked (change)="showSad = !showSad" />show sad</label
>)
</div>
<select [(ngModel)]="hero">
<span *ngFor="let h of heroes">
<span *ngIf="showSad || h.emotion !== 'sad'">
<option [ngValue]="h">{{ h.name }} ({{ h.emotion }})</option>
</span>
</span>
</select>
<p><em>&lt;select&gt; with &lt;ng-container&gt;</em></p>
<!-- #docregion select-ngcontainer -->
<div>
Pick your favorite hero (<label for="showSad"
><input id="showSad" type="checkbox" checked (change)="showSad = !showSad" />show sad</label
>)
</div>
<select [(ngModel)]="hero">
<ng-container *ngFor="let h of heroes">
<ng-container *ngIf="showSad || h.emotion !== 'sad'">
<option [ngValue]="h">{{ h.name }} ({{ h.emotion }})</option>
</ng-container>
</ng-container>
</select>
<!-- #enddocregion select-ngcontainer -->
<br /><br />
<hr />
<h2 id="ngFor">NgFor</h2>
<div class="box">
<p class="code">
&lt;div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
[class.odd]="odd"&gt;
</p>
<!--#docregion inside-ngfor -->
<div
*ngFor="let hero of heroes; let i = index; let odd = odd; trackBy: trackById"
[class.odd]="odd"
>
({{ i }}) {{ hero.name }}
</div>
<!--#enddocregion inside-ngfor -->
<p class="code">
&lt;ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
[ngForTrackBy]="trackById"/&gt;
</p>
<!--#docregion inside-ngfor -->
<ng-template
ngFor
let-hero
[ngForOf]="heroes"
let-i="index"
let-odd="odd"
[ngForTrackBy]="trackById"
>
<div [class.odd]="odd">({{ i }}) {{ hero.name }}</div>
</ng-template>
<!--#enddocregion inside-ngfor -->
</div>
<hr />
<h2 id="ngSwitch">NgSwitch</h2>
<div>Pick your favorite hero</div>
<p>
<label for="hero-{{ h }}" *ngFor="let h of heroes">
<input id="hero-{{ h }}" type="radio" name="heroes" [(ngModel)]="hero" [value]="h" />{{
h.name
}}
</label>
<label for="none-of-the-above"
><input id="none-of-the-above" type="radio" name="heroes" (click)="hero = null" />None of the
above</label
>
</p>
<h4>NgSwitch</h4>
<div [ngSwitch]="hero?.emotion">
<app-happy-hero *ngSwitchCase="'happy'" [hero]="hero!"></app-happy-hero>
<app-sad-hero *ngSwitchCase="'sad'" [hero]="hero!"></app-sad-hero>
<app-confused-hero *ngSwitchCase="'confused'" [hero]="hero!"></app-confused-hero>
<app-unknown-hero *ngSwitchDefault [hero]="hero!"></app-unknown-hero>
</div>
<h4>NgSwitch with &lt;ng-template&gt;</h4>
<div [ngSwitch]="hero?.emotion">
<ng-template ngSwitchCase="happy">
<app-happy-hero [hero]="hero!"></app-happy-hero>
</ng-template>
<ng-template ngSwitchCase="sad">
<app-sad-hero [hero]="hero!"></app-sad-hero>
</ng-template>
<ng-template ngSwitchCase="confused">
<app-confused-hero [hero]="hero!"></app-confused-hero>
</ng-template>
<ng-template ngSwitchDefault>
<app-unknown-hero [hero]="hero!"></app-unknown-hero>
</ng-template>
</div>
<hr />
<hr />
<h2 id="appIfLoaded">IfLoadedDirective</h2>
<app-hero></app-hero>
<hr />
<h2 id="appTrigonometry">TrigonometryDirective</h2>
<!-- #docregion appTrigonometry -->
<ul *appTrigonometry="30; sin as s; cos as c; tan as t">
<li>sin(30°): {{ s }}</li>
<li>cos(30°): {{ c }}</li>
<li>tan(30°): {{ t }}</li>
</ul>
<!-- #enddocregion appTrigonometry -->
@@ -1,30 +0,0 @@
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {heroSwitchComponents} from './hero-switch.components';
import {HeroComponent} from './hero.component';
import {TrigonometryDirective} from './trigonometry.directive';
import {Hero, heroes} from './hero';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
imports: [CommonModule, FormsModule, heroSwitchComponents, HeroComponent, TrigonometryDirective],
})
export class AppComponent {
heroes = heroes;
hero: Hero | null = this.heroes[0];
// #docregion condition
condition = false;
// #enddocregion condition
logs: string[] = [];
showSad = true;
status = 'ready';
trackById(index: number, hero: Hero): number {
return hero.id;
}
}
@@ -1,47 +0,0 @@
// #docregion
import {Component, computed, input} from '@angular/core';
import {Hero} from './hero';
@Component({
selector: 'app-happy-hero',
template: 'Wow. You like {{hero().name}}. What a happy hero ... just like you.',
})
export class HappyHeroComponent {
readonly hero = input.required<Hero>();
}
@Component({
selector: 'app-sad-hero',
template: 'You like {{hero().name}}? Such a sad hero. Are you sad too?',
})
export class SadHeroComponent {
readonly hero = input.required<Hero>();
}
@Component({
selector: 'app-confused-hero',
template: 'Are you as confused as {{hero().name}}?',
})
export class ConfusedHeroComponent {
readonly hero = input.required<Hero>();
}
@Component({
selector: 'app-unknown-hero',
template: '{{message()}}',
})
export class UnknownHeroComponent {
readonly hero = input.required<Hero | undefined>();
readonly message = computed(() => {
const heroName = this.hero()?.name;
return heroName ? `${heroName} is strange and mysterious.` : 'Are you feeling indecisive?';
});
}
export const heroSwitchComponents = [
HappyHeroComponent,
SadHeroComponent,
ConfusedHeroComponent,
UnknownHeroComponent,
];
@@ -1,23 +0,0 @@
import {Component} from '@angular/core';
import {JsonPipe} from '@angular/common';
import {IfLoadedDirective} from './if-loaded.directive';
import {LoadingState} from './loading-state';
import {Hero, heroes} from './hero';
@Component({
selector: 'app-hero',
template: `
<button (click)="onLoadHero()">Load Hero</button>
<p *appIfLoaded="heroLoadingState">{{ heroLoadingState.data | json }}</p>
`,
imports: [IfLoadedDirective, JsonPipe],
})
export class HeroComponent {
heroLoadingState: LoadingState<Hero> = {type: 'loading'};
onLoadHero(): void {
this.heroLoadingState = {type: 'loaded', data: heroes[0]};
}
}
@@ -1,13 +0,0 @@
// #docregion
export interface Hero {
id: number;
name: string;
emotion?: string;
}
export const heroes: Hero[] = [
{id: 1, name: 'Dr. Nice', emotion: 'happy'},
{id: 2, name: 'RubberMan', emotion: 'sad'},
{id: 3, name: 'Windstorm', emotion: 'confused'},
{id: 4, name: 'Magneta'},
];
@@ -1,34 +0,0 @@
import {Directive, effect, input, TemplateRef, ViewContainerRef} from '@angular/core';
import {Loaded, LoadingState} from './loading-state';
@Directive({
selector: '[appIfLoaded]',
})
export class IfLoadedDirective<T> {
private isViewCreated = false;
state = input.required<LoadingState<T>>({alias: 'appIfLoaded'});
constructor(
private readonly viewContainerRef: ViewContainerRef,
private readonly templateRef: TemplateRef<unknown>,
) {
effect(() => {
if (!this.isViewCreated && this.state().type === 'loaded') {
this.viewContainerRef.createEmbeddedView(this.templateRef);
this.isViewCreated = true;
} else if (this.isViewCreated && this.state().type !== 'loaded') {
this.viewContainerRef.clear();
this.isViewCreated = false;
}
});
}
static ngTemplateGuard_appIfLoaded<T>(
dir: IfLoadedDirective<T>,
state: LoadingState<T>,
): state is Loaded<T> {
return true;
}
}
@@ -1,5 +0,0 @@
export type Loaded<T> = {type: 'loaded'; data: T};
export type Loading = {type: 'loading'};
export type LoadingState<T> = Loaded<T> | Loading;
@@ -1,20 +0,0 @@
// interesting but unused code
heroChooser(picker: HTMLFieldSetElement) {
let choices = picker.children;
this.favoriteHero = undefined;
for (let i = 0; i < choices.length; i++) {
let choice = choices[i].children[0] as HTMLInputElement;
if (choice.checked) { this.favoriteHero = this.heroes[i]; }
}
}
<h4>Switch with *ngFor repeated switchCases using &lt;ng-container&gt;</h4>
<!-- #docregion NgSwitch-ngFor -->
<div [ngSwitch]="hero.id">
Your favorite hero is ...
<ng-container *ngFor="let hero of heroes">
<ng-container *ngSwitchCase="hero.id">{{hero.name}}</ng-container>
</ng-container>
<ng-container *ngSwitchDefault>None of the above</ng-container>
</div>
<!-- #enddocregion NgSwitch-ngFor -->
@@ -1,47 +0,0 @@
import {Directive, effect, input, TemplateRef, ViewContainerRef} from '@angular/core';
@Directive({
selector: '[appTrigonometry]',
})
export class TrigonometryDirective {
private isViewCreated = false;
private readonly context = new TrigonometryContext();
angleInDegrees = input.required<number>({alias: 'appTrigonometry'});
constructor(
private readonly viewContainerRef: ViewContainerRef,
private readonly templateRef: TemplateRef<TrigonometryContext>,
) {
effect(() => {
const angleInRadians = toRadians(this.angleInDegrees());
this.context.sin = Math.sin(angleInRadians);
this.context.cos = Math.cos(angleInRadians);
this.context.tan = Math.tan(angleInRadians);
if (!this.isViewCreated) {
this.viewContainerRef.createEmbeddedView(this.templateRef, this.context);
this.isViewCreated = true;
}
});
}
// Make sure the template checker knows the type of the context with which the
// template of this directive will be rendered
static ngTemplateContextGuard(
directive: TrigonometryDirective,
context: unknown,
): context is TrigonometryContext {
return true;
}
}
class TrigonometryContext {
sin = 0;
cos = 0;
tan = 0;
}
function toRadians(degrees: number): number {
return degrees * (Math.PI / 180);
}
@@ -1,14 +0,0 @@
<!doctype html>
<!-- #docregion -->
<html lang="en">
<head>
<title>Angular Structural Directives</title>
<base href="/" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<app-root></app-root>
</body>
</html>
@@ -1,5 +0,0 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
bootstrapApplication(AppComponent).catch((err) => console.error(err));
@@ -1,13 +0,0 @@
{
"description": "Structural directives",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!src/app/scrap.txt"
],
"file": "src/app/app.component.ts",
"tags": [
"structural", "directives", "template", "ngIf",
"ngSwitch", "ngFor"
]
}
@@ -1,6 +0,0 @@
{
"extends": "../../tools/examples/shared/boilerplate/cli/tsconfig.json",
"include": [
"*/**/*.ts",
],
}
@@ -1,204 +0,0 @@
import * as webdriver from 'selenium-webdriver';
describe('Angular v21 World', () => {
let driver: webdriver.WebDriver;
const isPresent = async (locator: webdriver.Locator) => {
const els = await driver.findElements(locator);
return els.length > 0;
};
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
beforeEach(async () => {
await driver.get('');
});
async function getCharacterPosition(): Promise<{x: string; y: string}> {
const character = await driver.findElement(webdriver.By.css('.character'));
const left = await character.getCssValue('left');
const top = await character.getCssValue('top');
return {x: left, y: top};
}
it('should display the initial game state correctly', async () => {
// Character is visible
expect(await (await driver.findElement(webdriver.By.css('.character'))).isDisplayed()).toBe(
true,
);
// Info sign is shown with welcome message
const infoSign = await driver.findElement(webdriver.By.css('.info-sign'));
expect(await infoSign.isDisplayed()).toBe(true);
expect(await infoSign.getAttribute('src')).toContain('welcome-sign.png');
// D-pad is visible
expect(await (await driver.findElement(webdriver.By.css('.d-pad'))).isDisplayed()).toBe(true);
// Explore button is not visible
expect(await isPresent(webdriver.By.css('.explore-button'))).toBe(false);
// No keys are present
expect((await driver.findElements(webdriver.By.css('.key-icon'))).length).toBe(0);
});
it('should move the character with the D-pad buttons', async () => {
const initialPosition = await getCharacterPosition();
const leftButton = await driver.findElement(webdriver.By.css('.d-pad-button.left'));
// Hold the button down for a short period to simulate walking
await driver.actions().move({origin: leftButton}).press().perform();
await sleep(200);
await driver.actions().release().perform();
const newPosition = await getCharacterPosition();
expect(newPosition.x).not.toEqual(initialPosition.x);
});
it('should move the character with keyboard arrow keys', async () => {
const initialPosition = await getCharacterPosition();
// Send arrow key press
const body = await driver.findElement(webdriver.By.css('body'));
await body.sendKeys(webdriver.Key.ARROW_RIGHT);
await sleep(200);
const newPosition = await getCharacterPosition();
expect(newPosition.x).not.toEqual(initialPosition.x);
});
it('should show explore button, open dialog, and collect a key when a destination is reached', async () => {
const body = await driver.findElement(webdriver.By.css('body'));
// Move left until we reach the Palm Tree destination
for (let i = 0; i < 20; i++) {
await body.sendKeys(webdriver.Key.ARROW_LEFT);
await sleep(100);
}
// Wait for the explore button to appear and check info sign
const exploreButtonLocator = webdriver.By.css('.explore-button');
await driver.wait(webdriver.until.elementLocated(exploreButtonLocator), 5000);
const exploreButton = await driver.findElement(exploreButtonLocator);
expect(await exploreButton.isDisplayed()).toBe(true);
expect(
await (await driver.findElement(webdriver.By.css('.info-sign'))).getAttribute('src'),
).toContain('enter-sign.png');
// Click explore button to open dialog
await exploreButton.click();
const dialogLocator = webdriver.By.css('.dialog-overlay');
await driver.wait(webdriver.until.elementLocated(dialogLocator), 1000);
const dialog = await driver.findElement(dialogLocator);
expect(await dialog.isDisplayed()).toBe(true);
expect(await (await dialog.findElement(webdriver.By.css('h2'))).getText()).toEqual(
"What's new in Angular AI",
);
// Close the dialog
await (await dialog.findElement(webdriver.By.css('.close-button'))).click();
await driver.wait(async () => !(await isPresent(dialogLocator)), 1000);
expect(await isPresent(dialogLocator)).toBe(false);
// Check that one key has been collected
expect((await driver.findElements(webdriver.By.css('.key-icon'))).length).toBe(1);
});
it('should show entry denied at castle without all keys', async () => {
const body = await driver.findElement(webdriver.By.css('body'));
// Move to a position near the castle without collecting keys
for (let i = 0; i < 20; i++) {
await body.sendKeys(webdriver.Key.ARROW_RIGHT);
await sleep(100);
}
for (let i = 0; i < 20; i++) {
await body.sendKeys(webdriver.Key.ARROW_DOWN);
await sleep(100);
}
await sleep(1000); // Settle
// Check that the entry denied sign is shown and the button is not present
expect(
await (await driver.findElement(webdriver.By.css('.info-sign'))).getAttribute('src'),
).toContain('entry-denied-sign.png');
expect(await isPresent(webdriver.By.css('.explore-button'))).toBe(false);
});
it('should handle the full game flow and show congrats state', async () => {
const body = await driver.findElement(webdriver.By.css('body'));
const exploreButtonLocator = webdriver.By.css('.explore-button');
const dialogLocator = webdriver.By.css('.dialog-overlay');
const mascotLocator = webdriver.By.css('.mascot-icon');
// **Navigate to Palm Tree (d1) and collect the first key**
for (let i = 0; i < 20; i++) {
await body.sendKeys(webdriver.Key.ARROW_LEFT);
await sleep(100);
}
await driver.wait(webdriver.until.elementLocated(exploreButtonLocator), 5000);
await (await driver.findElement(exploreButtonLocator)).click();
await driver.wait(webdriver.until.elementLocated(dialogLocator), 1000);
let dialog = await driver.findElement(dialogLocator);
await (await dialog.findElement(webdriver.By.css('.close-button'))).click();
await driver.wait(async () => !(await isPresent(dialogLocator)), 1000);
expect((await driver.findElements(webdriver.By.css('.key-icon'))).length).toBe(1);
// **Navigate to Red Door (d2) and collect the second key**
for (let i = 0; i < 15; i++) {
await body.sendKeys(webdriver.Key.ARROW_UP);
await sleep(100);
}
await driver.wait(webdriver.until.elementLocated(exploreButtonLocator), 5000);
await (await driver.findElement(exploreButtonLocator)).click();
await driver.wait(webdriver.until.elementLocated(dialogLocator), 1000);
dialog = await driver.findElement(dialogLocator);
await (await dialog.findElement(webdriver.By.css('.close-button'))).click();
await driver.wait(async () => !(await isPresent(dialogLocator)), 1000);
expect((await driver.findElements(webdriver.By.css('.key-icon'))).length).toBe(2);
// **Navigate to Volcano (d3) and collect the third key**
for (let i = 0; i < 25; i++) {
await body.sendKeys(webdriver.Key.ARROW_RIGHT);
await sleep(100);
}
await driver.wait(webdriver.until.elementLocated(exploreButtonLocator), 5000);
await (await driver.findElement(exploreButtonLocator)).click();
await driver.wait(webdriver.until.elementLocated(dialogLocator), 1000);
dialog = await driver.findElement(dialogLocator);
await (await dialog.findElement(webdriver.By.css('.close-button'))).click();
await driver.wait(async () => !(await isPresent(dialogLocator)), 1000);
expect((await driver.findElements(webdriver.By.css('.key-icon'))).length).toBe(3);
// **Navigate to Castle (d4) with all keys**
for (let i = 0; i < 15; i++) {
await body.sendKeys(webdriver.Key.ARROW_DOWN);
await sleep(100);
}
await sleep(1000); // Wait for character to settle
// Check for correct sign and button visibility
expect(
await (await driver.findElement(webdriver.By.css('.info-sign'))).getAttribute('src'),
).toContain('castle-sign.png');
await driver.wait(webdriver.until.elementLocated(exploreButtonLocator), 5000);
// **Open Castle dialog, close it, and see mascot and final sign**
await (await driver.findElement(exploreButtonLocator)).click();
await driver.wait(webdriver.until.elementLocated(dialogLocator), 1000);
dialog = await driver.findElement(dialogLocator);
await (await dialog.findElement(webdriver.By.css('.close-button'))).click();
await driver.wait(async () => !(await isPresent(dialogLocator)), 1000);
// Mascot appears
await driver.wait(webdriver.until.elementLocated(mascotLocator), 1000);
const mascot = await driver.findElement(mascotLocator);
expect(await mascot.isDisplayed()).toBe(true);
// Congrats sign appears
expect(
await (await driver.findElement(webdriver.By.css('.info-sign'))).getAttribute('src'),
).toContain('congrats-sign.png');
});
});