mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
@@ -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
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
<span>{{ item()?.name }}</span>
|
||||
</div>
|
||||
-12
@@ -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><ng-template> 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"><ng-container></h2>
|
||||
|
||||
<h4>*ngIf with a <ng-container></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><select> with <span></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><select> with <ng-container></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">
|
||||
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
|
||||
[class.odd]="odd">
|
||||
</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">
|
||||
<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
|
||||
[ngForTrackBy]="trackById"/>
|
||||
</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 <ng-template></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 <ng-container></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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user