chore(): sync with main

This commit is contained in:
Liam DeBeasi
2022-11-01 10:31:18 -04:00
159 changed files with 988 additions and 1184 deletions

View File

@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.3](https://github.com/ionic-team/ionic-framework/compare/v6.3.2...v6.3.3) (2022-10-26)
### Bug Fixes
* **datetime:** empty string is treated as no value ([#26131](https://github.com/ionic-team/ionic-framework/issues/26131)) ([51ab5f6](https://github.com/ionic-team/ionic-framework/commit/51ab5f67b50013c0ed8ca3160d6dfc56bc269f2a)), closes [#26116](https://github.com/ionic-team/ionic-framework/issues/26116)
* **datetime:** preferWheel can now show title ([#26101](https://github.com/ionic-team/ionic-framework/issues/26101)) ([479d56b](https://github.com/ionic-team/ionic-framework/commit/479d56b3b26d45bfd03d4095458c37ed00485c54)), closes [#26095](https://github.com/ionic-team/ionic-framework/issues/26095)
* **datetime:** values are adjusted to be in bounds ([#26125](https://github.com/ionic-team/ionic-framework/issues/26125)) ([0548fe8](https://github.com/ionic-team/ionic-framework/commit/0548fe858854f0187e0dfe00efaec142cd5bb6cf)), closes [#25894](https://github.com/ionic-team/ionic-framework/issues/25894) [#25708](https://github.com/ionic-team/ionic-framework/issues/25708)
* **dependencies:** latest patch is installed ([#26148](https://github.com/ionic-team/ionic-framework/issues/26148)) ([74be79e](https://github.com/ionic-team/ionic-framework/commit/74be79e9d81fd5431ae2fc442fd6387cf37b2015)), closes [#26137](https://github.com/ionic-team/ionic-framework/issues/26137)
* **many:** haptics only fire on supported platforms ([#26130](https://github.com/ionic-team/ionic-framework/issues/26130)) ([d4d569a](https://github.com/ionic-team/ionic-framework/commit/d4d569ac09ab25ab5a490825cf1fc655fe97bb87)), closes [#26109](https://github.com/ionic-team/ionic-framework/issues/26109)
* **react:** inline overlays can be conditionally rendered ([#26111](https://github.com/ionic-team/ionic-framework/issues/26111)) ([8ec350a](https://github.com/ionic-team/ionic-framework/commit/8ec350ae652095ae29e2f02a7f105cb709a72583)), closes [#25590](https://github.com/ionic-team/ionic-framework/issues/25590)
## [6.3.2](https://github.com/ionic-team/ionic-framework/compare/v6.3.1...v6.3.2) (2022-10-17)

View File

@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.3](https://github.com/ionic-team/ionic/compare/v6.3.2...v6.3.3) (2022-10-26)
### Bug Fixes
* **datetime:** empty string is treated as no value ([#26131](https://github.com/ionic-team/ionic/issues/26131)) ([51ab5f6](https://github.com/ionic-team/ionic/commit/51ab5f67b50013c0ed8ca3160d6dfc56bc269f2a)), closes [#26116](https://github.com/ionic-team/ionic/issues/26116)
* **dependencies:** latest patch is installed ([#26148](https://github.com/ionic-team/ionic/issues/26148)) ([74be79e](https://github.com/ionic-team/ionic/commit/74be79e9d81fd5431ae2fc442fd6387cf37b2015)), closes [#26137](https://github.com/ionic-team/ionic/issues/26137)
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
**Note:** Version bump only for package @ionic/angular

View File

@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "6.3.2",
"version": "6.3.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "6.3.2",
"version": "6.3.3",
"license": "MIT",
"dependencies": {
"@ionic/core": "6.3.2",
"@ionic/core": "^6.3.3",
"jsonc-parser": "^3.0.0",
"tslib": "^2.0.0"
},
@ -1023,9 +1023,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.3.tgz",
"integrity": "sha512-I7eRsni/3blStScsQcITb4seuunNzS4mIK0GxHljeRRb8oQI9Xy/oP4rq9weo9lsuEVD0sHIqBIS7/Oo9iEymQ==",
"dependencies": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@ -7951,9 +7951,9 @@
"dev": true
},
"@ionic/core": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.3.tgz",
"integrity": "sha512-I7eRsni/3blStScsQcITb4seuunNzS4mIK0GxHljeRRb8oQI9Xy/oP4rq9weo9lsuEVD0sHIqBIS7/Oo9iEymQ==",
"requires": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",

View File

@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "6.3.2",
"version": "6.3.3",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@ -46,7 +46,7 @@
"validate": "npm i && npm run lint && npm run test && npm run build"
},
"dependencies": {
"@ionic/core": "6.3.2",
"@ionic/core": "^6.3.3",
"jsonc-parser": "^3.0.0",
"tslib": "^2.0.0"
},

View File

@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.3](https://github.com/ionic-team/ionic/compare/v6.3.2...v6.3.3) (2022-10-26)
### Bug Fixes
* **datetime:** empty string is treated as no value ([#26131](https://github.com/ionic-team/ionic/issues/26131)) ([51ab5f6](https://github.com/ionic-team/ionic/commit/51ab5f67b50013c0ed8ca3160d6dfc56bc269f2a)), closes [#26116](https://github.com/ionic-team/ionic/issues/26116)
* **datetime:** preferWheel can now show title ([#26101](https://github.com/ionic-team/ionic/issues/26101)) ([479d56b](https://github.com/ionic-team/ionic/commit/479d56b3b26d45bfd03d4095458c37ed00485c54)), closes [#26095](https://github.com/ionic-team/ionic/issues/26095)
* **datetime:** values are adjusted to be in bounds ([#26125](https://github.com/ionic-team/ionic/issues/26125)) ([0548fe8](https://github.com/ionic-team/ionic/commit/0548fe858854f0187e0dfe00efaec142cd5bb6cf)), closes [#25894](https://github.com/ionic-team/ionic/issues/25894) [#25708](https://github.com/ionic-team/ionic/issues/25708)
* **many:** haptics only fire on supported platforms ([#26130](https://github.com/ionic-team/ionic/issues/26130)) ([d4d569a](https://github.com/ionic-team/ionic/commit/d4d569ac09ab25ab5a490825cf1fc655fe97bb87)), closes [#26109](https://github.com/ionic-team/ionic/issues/26109)
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)

View File

@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "6.3.2",
"version": "6.3.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "6.3.2",
"version": "6.3.3",
"license": "MIT",
"dependencies": {
"@stencil/core": "^2.18.0",

View File

@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "6.3.2",
"version": "6.3.3",
"description": "Base components for Ionic",
"keywords": [
"ionic",

View File

@ -1,11 +1,11 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import type { PlaywrightTestConfig, PlaywrightTestOptions, PlaywrightWorkerOptions, Project } from '@playwright/test';
import { devices, expect } from '@playwright/test';
import { matchers } from './src/utils/test/playwright';
expect.extend(matchers);
const projects = [
const projects: Project<PlaywrightTestOptions, PlaywrightWorkerOptions>[] = [
{
/**
* This is really just desktop Firefox
@ -24,10 +24,6 @@ const projects = [
width: 393,
height: 727
},
screen: {
width: 393,
height: 851
}
},
},
{
@ -49,7 +45,7 @@ const projects = [
const modes = ['ios', 'md'];
const generateProjects = () => {
const projectsWithMetadata = [];
const projectsWithMetadata: Project<PlaywrightTestOptions, PlaywrightWorkerOptions>[] = [];
modes.forEach(mode => {
projects.forEach(project => {
@ -105,9 +101,6 @@ const config: PlaywrightTestConfig = {
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/**
* All failed tests should create
* a trace file for easier debugging.

View File

@ -36,6 +36,7 @@ import { is24Hour, isLocaleDayPeriodRTL, isMonthFirstLocale, getNumDaysInMonth }
import {
calculateHourFromAMPM,
convertDataToISO,
getClosestValidDate,
getEndOfWeek,
getNextDay,
getNextMonth,
@ -96,7 +97,8 @@ export class Datetime implements ComponentInterface {
private minParts?: any;
private maxParts?: any;
private todayParts = parseDate(getToday());
private todayParts!: DatetimeParts;
private defaultParts!: DatetimeParts;
private prevPresentation: string | null = null;
@ -571,13 +573,13 @@ export class Datetime implements ComponentInterface {
* may not be set. This function works
* by returning the first selected date in
* "activePartsClone" and then falling back to
* today's DatetimeParts if no active date is selected.
* defaultParts if no active date is selected.
*/
private getDefaultPart = (): DatetimeParts => {
const { activePartsClone, todayParts } = this;
private getActivePartsWithFallback = () => {
const { activePartsClone, defaultParts } = this;
const firstPart = Array.isArray(activePartsClone) ? activePartsClone[0] : activePartsClone;
return firstPart ?? todayParts;
return firstPart ?? defaultParts;
};
private closeParentOverlay = () => {
@ -792,24 +794,24 @@ export class Datetime implements ComponentInterface {
};
private processMinParts = () => {
const { min, todayParts } = this;
const { min, defaultParts } = this;
if (min === undefined) {
this.minParts = undefined;
return;
}
this.minParts = parseMinParts(min, todayParts);
this.minParts = parseMinParts(min, defaultParts);
};
private processMaxParts = () => {
const { max, todayParts } = this;
const { max, defaultParts } = this;
if (max === undefined) {
this.maxParts = undefined;
return;
}
this.maxParts = parseMaxParts(max, todayParts);
this.maxParts = parseMaxParts(max, defaultParts);
};
private initializeCalendarListener = () => {
@ -1166,8 +1168,8 @@ export class Datetime implements ComponentInterface {
}
private processValue = (value?: string | string[] | null) => {
const hasValue = value !== null && value !== undefined;
const valueToProcess = parseDate(value ?? getToday());
const hasValue = value !== '' && value !== null && value !== undefined;
let valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
const { minParts, maxParts } = this;
@ -1241,12 +1243,16 @@ export class Datetime implements ComponentInterface {
this.processMinParts();
this.processMaxParts();
const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues));
const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues));
const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues));
const yearValues = (this.parsedYearValues = convertToArrayOfNumbers(this.yearValues));
const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues));
const todayParts = (this.todayParts = parseDate(getToday()));
this.defaultParts = getClosestValidDate(todayParts, monthValues, dayValues, yearValues, hourValues, minuteValues);
this.processValue(this.value);
this.parsedHourValues = convertToArrayOfNumbers(this.hourValues);
this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues);
this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues);
this.parsedYearValues = convertToArrayOfNumbers(this.yearValues);
this.parsedDayValues = convertToArrayOfNumbers(this.dayValues);
this.emitStyle();
}
@ -1401,9 +1407,9 @@ export class Datetime implements ComponentInterface {
}
private renderCombinedDatePickerColumn() {
const { workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
const { defaultParts, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
/**
* By default, generate a range of 3 months:
@ -1469,12 +1475,12 @@ export class Datetime implements ComponentInterface {
/**
* If we have selected a day already, then default the column
* to that value. Otherwise, default it to today.
* to that value. Otherwise, set it to the default date.
*/
const todayString =
workingParts.day !== null
? `${workingParts.year}-${workingParts.month}-${workingParts.day}`
: `${todayParts.year}-${todayParts.month}-${todayParts.day}`;
: `${defaultParts.year}-${defaultParts.month}-${defaultParts.day}`;
return (
<ion-picker-column-internal
@ -1560,7 +1566,7 @@ export class Datetime implements ComponentInterface {
const shouldRenderYears = forcePresentation !== 'month' && forcePresentation !== 'time';
const years = shouldRenderYears
? getYearColumnData(this.locale, this.todayParts, this.minParts, this.maxParts, this.parsedYearValues)
? getYearColumnData(this.locale, this.defaultParts, this.minParts, this.maxParts, this.parsedYearValues)
: [];
/**
@ -1593,14 +1599,14 @@ export class Datetime implements ComponentInterface {
const { workingParts } = this;
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
class="day-column"
color={this.color}
items={days}
value={(workingParts.day !== null ? workingParts.day : this.todayParts.day) ?? undefined}
value={(workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined}
onIonChange={(ev: CustomEvent) => {
// TODO(FW-1823) Remove this when iOS 14 support is dropped.
// Due to a Safari 14 issue we need to destroy
@ -1637,7 +1643,7 @@ export class Datetime implements ComponentInterface {
const { workingParts } = this;
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
@ -1680,7 +1686,7 @@ export class Datetime implements ComponentInterface {
const { workingParts } = this;
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
@ -1744,7 +1750,7 @@ export class Datetime implements ComponentInterface {
const { workingParts } = this;
if (hoursData.length === 0) return [];
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
@ -1772,7 +1778,7 @@ export class Datetime implements ComponentInterface {
const { workingParts } = this;
if (minutesData.length === 0) return [];
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
@ -1802,7 +1808,7 @@ export class Datetime implements ComponentInterface {
return [];
}
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
const isDayPeriodRTL = isLocaleDayPeriodRTL(this.locale);
return (
@ -1916,7 +1922,7 @@ export class Datetime implements ComponentInterface {
// can free-scroll the calendar.
const isWorkingMonth = this.workingParts.month === month && this.workingParts.year === year;
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
return (
<div
@ -2046,7 +2052,7 @@ export class Datetime implements ComponentInterface {
private renderTimeOverlay() {
const use24Hour = is24Hour(this.locale, this.hourCycle);
const activePart = this.getDefaultPart();
const activePart = this.getActivePartsWithFallback();
return [
<div class="time-header">{this.renderTimeLabel()}</div>,
@ -2127,7 +2133,7 @@ export class Datetime implements ComponentInterface {
}
} else {
// for exactly 1 day selected (multiple set or not), show a formatted version of that
headerText = getMonthAndDay(this.locale, this.getDefaultPart());
headerText = getMonthAndDay(this.locale, this.getActivePartsWithFallback());
}
return headerText;

View File

@ -2,6 +2,10 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('datetime: values', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
skip.mode('md');
});
test('should render correct days', async ({ page }) => {
await page.setContent(`
<ion-datetime locale="en-US" presentation="date" day-values="1,2,3"></ion-datetime>
@ -49,4 +53,103 @@ test.describe('datetime: values', () => {
const items = page.locator('ion-picker-column-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)');
await expect(items).toHaveText(['01', '02', '03']);
});
test('should adjust default parts for allowed hour and minute values', async ({ page }) => {
/**
* Mock today's date for testing.
* Playwright does not support this natively
* so we extend the native Date interface: https://github.com/microsoft/playwright/issues/6347
*/
await page.setContent(`
<ion-datetime presentation="time" locale="en-US" hour-values="02" minute-values="0,15,30,45"></ion-datetime>
<script>
const mockToday = '2022-10-10T16:22';
Date = class extends Date {
constructor(...args) {
if (args.length === 0) {
super(mockToday)
} else {
super(...args);
}
}
}
</script>
`);
await page.waitForSelector('.datetime-ready');
const minuteItems = page.locator('ion-picker-column-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)');
await expect(minuteItems).toHaveText(['00', '15', '30', '45']);
await expect(minuteItems.nth(1)).toHaveClass(/picker-item-active/);
const hourItems = page.locator('ion-picker-column-internal:nth-of-type(1) .picker-item:not(.picker-item-empty)');
await expect(hourItems).toHaveText(['2']);
await expect(hourItems.nth(0)).toHaveClass(/picker-item-active/);
/**
* Since the allowed hour is 2AM, the time period
* should switch from PM to AM.
*/
const ampmItems = page.locator('ion-picker-column-internal:nth-of-type(3) .picker-item:not(.picker-item-empty)');
await expect(ampmItems).toHaveText(['AM', 'PM']);
await expect(ampmItems.nth(0)).toHaveClass(/picker-item-active/);
});
test('should adjust default parts month for allowed month values', async ({ page }) => {
/**
* Mock today's date for testing.
* Playwright does not support this natively
* so we extend the native Date interface: https://github.com/microsoft/playwright/issues/6347
*/
await page.setContent(`
<ion-datetime prefer-wheel="true" presentation="date" locale="en-US" month-values="01" hour-values="02" minute-values="0,15,30,45"></ion-datetime>
<script>
const mockToday = '2022-10-10T16:22';
Date = class extends Date {
constructor(...args) {
if (args.length === 0) {
super(mockToday)
} else {
super(...args);
}
}
}
</script>
`);
await page.waitForSelector('.datetime-ready');
const monthItems = page.locator('.month-column .picker-item:not(.picker-item-empty)');
await expect(monthItems).toHaveText(['January']);
await expect(monthItems.nth(0)).toHaveClass(/picker-item-active/);
});
test('today date highlight should persist even if disallowed from dayValues', async ({ page }) => {
/**
* Mock today's date for testing.
* Playwright does not support this natively
* so we extend the native Date interface: https://github.com/microsoft/playwright/issues/6347
*/
await page.setContent(`
<ion-datetime day-values="9" presentation="date" locale="en-US"></ion-datetime>
<script>
const mockToday = '2022-10-10T16:22';
Date = class extends Date {
constructor(...args) {
if (args.length === 0) {
super(mockToday)
} else {
super(...args);
}
}
}
</script>
`);
await page.waitForSelector('.datetime-ready');
const todayButton = page.locator('.calendar-day[data-day="10"][data-month="10"][data-year="2022"]');
await expect(todayButton).toHaveClass(/calendar-day-today/);
});
});

View File

@ -2,6 +2,7 @@ import type { DatetimeParts } from '../datetime-interface';
import { isSameDay } from './comparison';
import { getNumDaysInMonth } from './helpers';
import { parseAmPm } from './parse';
const twoDigit = (val: number | undefined): string => {
return ('0' + (val !== undefined ? Math.abs(val) : '0')).slice(-2);
@ -417,3 +418,83 @@ export const validateParts = (
return partsCopy;
};
/**
* Returns the closest date to refParts
* that also meets the constraints of
* the *Values params.
* @param refParts The reference date
* @param monthValues The allowed month values
* @param dayValues The allowed day (of the month) values
* @param yearValues The allowed year values
* @param hourValues The allowed hour values
* @param minuteValues The allowed minute values
*/
export const getClosestValidDate = (
refParts: DatetimeParts,
monthValues?: number[],
dayValues?: number[],
yearValues?: number[],
hourValues?: number[],
minuteValues?: number[]
) => {
const { hour, minute, day, month, year } = refParts;
const copyParts = { ...refParts, dayOfWeek: undefined };
if (monthValues !== undefined) {
copyParts.month = findClosestValue(month, monthValues);
}
// Day is nullable but cannot be undefined
if (day !== null && dayValues !== undefined) {
copyParts.day = findClosestValue(day, dayValues);
}
if (yearValues !== undefined) {
copyParts.year = findClosestValue(year, yearValues);
}
if (hour !== undefined && hourValues !== undefined) {
copyParts.hour = findClosestValue(hour, hourValues);
copyParts.ampm = parseAmPm(copyParts.hour);
}
if (minute !== undefined && minuteValues !== undefined) {
copyParts.minute = findClosestValue(minute, minuteValues);
}
return copyParts;
};
/**
* Finds the value in "values" that is
* numerically closest to "reference".
* This function assumes that "values" is
* already sorted in ascending order.
* @param reference The reference number to use
* when finding the closest value
* @param values The allowed values that will be
* searched to find the closest value to "reference"
*/
const findClosestValue = (reference: number, values: number[]) => {
let closestValue = values[0];
let rank = Math.abs(closestValue - reference);
for (let i = 1; i < values.length; i++) {
const value = values[i];
/**
* This code prioritizes the first
* closest result. Given two values
* with the same distance from reference,
* this code will prioritize the smaller of
* the two values.
*/
const valueRank = Math.abs(value - reference);
if (valueRank < rank) {
closestValue = value;
rank = valueRank;
}
}
return closestValue;
};

View File

@ -1,11 +0,0 @@
import { AxePuppeteer } from '@axe-core/puppeteer';
import { newE2EPage } from '@stencil/core/testing';
test('header: axe', async () => {
const page = await newE2EPage({
url: '/src/components/header/test/a11y?ionic:_testing=true',
});
const results = await new AxePuppeteer(page).analyze();
expect(results.violations.length).toEqual(0);
});

View File

@ -0,0 +1,34 @@
import AxeBuilder from '@axe-core/playwright';
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('header: a11y', () => {
test.beforeEach(async ({ skip }) => {
skip.rtl();
skip.mode('md');
});
test('should not have accessibility violations', async ({ page }) => {
await page.goto(`/src/components/header/test/a11y`);
const headers = page.locator('ion-header');
await expect(headers.first()).toHaveAttribute('role', 'banner');
await expect(headers.last()).toHaveAttribute('role', 'none');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test('should allow for custom role', async ({ page }) => {
/**
* Note: This example should not be used in production.
* This only serves to check that `role` can be customized.
*/
await page.setContent(`
<ion-header role="heading"></ion-header>
`);
const header = page.locator('ion-header');
await expect(header).toHaveAttribute('role', 'heading');
});
});

View File

@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('header: basic', async () => {
const page = await newE2EPage({
url: '/src/components/header/test/basic?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -0,0 +1,78 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('header: basic', () => {
test.describe('header: rendering', () => {
test('should not have visual regressions with basic header', async ({ page }) => {
await page.setContent(`
<ion-header>
<ion-toolbar>
<ion-title>Header - Default</ion-title>
</ion-toolbar>
</ion-header>
`);
const header = page.locator('ion-header');
expect(await header.screenshot()).toMatchSnapshot(`header-diff-${page.getSnapshotSettings()}.png`);
});
});
test.describe('header: feature rendering', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
});
test('should not have visual regressions with no border', async ({ page }) => {
await page.setContent(`
<ion-header class="ion-no-border">
<ion-toolbar>
<ion-title>Header - No Border</ion-title>
</ion-toolbar>
</ion-header>
`);
const header = page.locator('ion-header');
expect(await header.screenshot()).toMatchSnapshot(`header-no-border-diff-${page.getSnapshotSettings()}.png`);
});
test('should not have visual regressions with translucent header', async ({ page, skip }) => {
skip.mode('md', 'Translucent effect is only available in iOS mode.');
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
await page.setContent(`
<ion-header translucent="true">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
<img style="transform: rotate(145deg) scale(1.5)" src="/src/components/header/test/img.jpg" />
</div>
<ion-toolbar>
<ion-title>Header - Translucent</ion-title>
</ion-toolbar>
</ion-header>
`);
const header = page.locator('ion-header');
expect(await header.screenshot()).toMatchSnapshot(`header-translucent-diff-${page.getSnapshotSettings()}.png`);
});
test('should not have visual regressions with translucent header with color', async ({ page, skip }) => {
skip.mode('md', 'Translucent effect is only available in iOS mode.');
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
await page.setContent(`
<ion-header translucent="true">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
<img style="transform: rotate(145deg) scale(1.5)" src="/src/components/header/test/img.jpg" />
</div>
<ion-toolbar color="tertiary">
<ion-title>Header - Translucent</ion-title>
</ion-toolbar>
</ion-header>
`);
const header = page.locator('ion-header');
expect(await header.screenshot()).toMatchSnapshot(
`header-translucent-color-diff-${page.getSnapshotSettings()}.png`
);
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -15,20 +15,13 @@
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-header>
<ion-toolbar>
<ion-title>Header - Default</ion-title>
</ion-toolbar>
</ion-header>
<br />
<ion-header class="ion-no-border">
<ion-toolbar>
<ion-title>Header - No Border</ion-title>
</ion-toolbar>
</ion-header>
<h1>Content</h1>
</ion-content>
</ion-app>
</body>

View File

@ -7,24 +7,37 @@ test.describe('header: condense', () => {
skip.rtl();
await page.goto('/src/components/header/test/condense');
const header = page.locator('#collapsibleHeader');
const largeTitleHeader = page.locator('#largeTitleHeader');
const smallTitleHeader = page.locator('#smallTitleHeader');
const content = page.locator('ion-content');
await expect(header).toHaveAttribute('aria-hidden', 'true');
await expect(smallTitleHeader).toHaveAttribute('aria-hidden', 'true');
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom());
await page.waitForChanges();
expect(await largeTitleHeader.screenshot({ animations: 'disabled' })).toMatchSnapshot(
`header-condense-large-title-initial-diff-${page.getSnapshotSettings()}.png`
);
await content.evaluate(async (el: HTMLIonContentElement) => {
await el.scrollToBottom();
});
await page.waitForSelector('#largeTitleHeader.header-collapse-condense-inactive');
expect(await smallTitleHeader.screenshot({ animations: 'disabled' })).toMatchSnapshot(
`header-condense-large-title-collapsed-diff-${page.getSnapshotSettings()}.png`
);
/**
* Playwright can't do .not.toHaveAttribute() because a value is expected,
* and toHaveAttribute can't accept a value of type null.
*/
const ariaHidden = await header.getAttribute('aria-hidden');
const ariaHidden = await smallTitleHeader.getAttribute('aria-hidden');
expect(ariaHidden).toBeNull();
await content.evaluate((el: HTMLIonContentElement) => el.scrollToTop());
await page.waitForChanges();
await content.evaluate(async (el: HTMLIonContentElement) => {
await el.scrollToTop();
});
await page.waitForSelector('#smallTitleHeader.header-collapse-condense-inactive');
await expect(header).toHaveAttribute('aria-hidden', 'true');
await expect(smallTitleHeader).toHaveAttribute('aria-hidden', 'true');
});
});

View File

@ -12,79 +12,74 @@
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
.grid-item {
height: 200px;
}
</style>
</head>
<body>
<ion-app>
<div class="ion-page">
<ion-header translucent="true" id="collapsibleHeader">
<ion-header translucent="true" id="smallTitleHeader">
<div style="position: absolute; left: 0; right: 0; bottom: 0">
<img style="transform: rotate(180deg)" src="/src/components/header/test/img.jpg" />
</div>
<ion-toolbar>
<ion-title>Mailboxes</ion-title>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content fullscreen="true">
<ion-header collapse="condense">
<ion-header collapse="condense" id="largeTitleHeader">
<ion-toolbar>
<ion-title size="large">Mailboxes</ion-title>
<ion-title size="large">Header</ion-title>
</ion-toolbar>
</ion-header>
<div class="grid ion-padding">
<div class="grid-item red"></div>
<div class="grid-item green"></div>
<div class="grid-item blue"></div>
<div class="grid-item yellow"></div>
<div class="grid-item pink"></div>
<div class="grid-item purple"></div>
<div class="grid-item black"></div>
<div class="grid-item orange"></div>
<div class="ion-padding">
<h1>Content</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit, justo vitae rhoncus porttitor,
massa elit molestie nisl, ut tincidunt orci enim ac ante. Vestibulum tincidunt dignissim elit. Morbi
cursus hendrerit turpis, ut egestas tortor pulvinar sit amet. Interdum et malesuada fames ac ante ipsum
primis in faucibus. Phasellus faucibus consequat purus vel mollis. Ut ultricies elit nunc. Quisque
ultrices turpis vel augue auctor accumsan. Donec bibendum at nisi vel finibus. Fusce id imperdiet odio.
Morbi orci ipsum, imperdiet ut iaculis sit amet, suscipit vulputate felis. Nullam arcu leo, volutpat eu
porttitor sed, fringilla et arcu. Pellentesque ac libero sapien. Quisque id dui velit. Mauris et pretium
tortor.
</p>
<p>
Ut ultricies id augue vel aliquam. Etiam ornare finibus nisl, nec egestas urna. Nam pellentesque libero
nec justo tristique lacinia. In sit amet gravida metus, ac tincidunt mauris. Fusce sit amet tempus turpis.
Nulla ligula nunc, vestibulum quis quam in, feugiat aliquet nibh. Quisque in ante non nulla luctus gravida
vel at lacus. Vivamus erat magna, elementum et dignissim nec, posuere eu ante. Praesent elementum, arcu
scelerisque venenatis sodales, turpis nulla aliquam urna, id hendrerit est orci et purus. Duis sem ipsum,
imperdiet eu elit id, tincidunt tempus sapien. Praesent tincidunt, sapien sed rhoncus euismod, lectus
velit ornare nunc, dapibus varius turpis leo ut magna.
</p>
<p>
Nam quis quam id ante mattis pulvinar non sed mauris. Donec tempor sed nulla at semper. Vivamus ac nunc
bibendum, ullamcorper lacus quis, ornare massa. Cras gravida nibh risus, id sollicitudin eros ultricies
non. Integer velit massa, suscipit tincidunt rhoncus ut, lacinia et nisl. Maecenas volutpat ipsum blandit
sollicitudin lobortis. Suspendisse potenti. Cras non mi non arcu varius dapibus. Suspendisse maximus eget
justo a lobortis. Donec nulla ipsum, efficitur eget velit eget, varius rutrum quam. Nulla metus risus,
accumsan a tellus ac, faucibus blandit quam. Donec luctus, nisl ac ultricies ornare, nunc elit finibus
magna, id elementum ante urna congue ex. Cras condimentum nisi sollicitudin tortor vestibulum luctus.
Curabitur non ipsum et ex vestibulum congue.
</p>
<p>
Maecenas rhoncus elit ut consectetur faucibus. Etiam sed sem sed mauris condimentum viverra sit amet at
nibh. Mauris bibendum at purus a cursus. Suspendisse potenti. Donec vel lacus ac odio euismod lacinia id
in urna. Donec commodo ipsum augue, at bibendum ex convallis suscipit. Nulla ac rhoncus odio. Aenean
elementum est nec arcu ultricies dignissim.
</p>
<p>
Sed tincidunt bibendum massa, egestas bibendum est imperdiet vitae. Fusce dignissim consectetur ante a
fermentum. Morbi suscipit turpis sapien. Suspendisse eleifend sapien eget nunc mattis mattis. Phasellus
rhoncus sodales libero a imperdiet. Nam in vulputate lectus. Proin accumsan enim non nibh sagittis
ultricies. Nullam vitae ultricies nunc. Nullam ultrices dolor nec vehicula posuere.
</p>
</div>
</ion-content>
<ion-footer translucent="true">
<ion-toolbar>
<ion-title>Updated Just Now</ion-title>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</div>

View File

@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('header: fade', async () => {
const page = await newE2EPage({
url: '/src/components/header/test/fade?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -0,0 +1,28 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('header: fade', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
});
test('should not have visual regressions with fade header', async ({ page, skip }) => {
skip.mode('md', 'Translucent effect is only available in iOS mode.');
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
await page.goto('/src/components/header/test/fade');
const header = page.locator('ion-header');
expect(await header.screenshot({ animations: 'disabled' })).toMatchSnapshot(
`header-fade-not-blurred-diff-${page.getSnapshotSettings()}.png`
);
const content = page.locator('ion-content');
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await page.waitForChanges();
expect(await header.screenshot({ animations: 'disabled' })).toMatchSnapshot(
`header-fade-blurred-diff-${page.getSnapshotSettings()}.png`
);
});
});

View File

@ -12,74 +12,69 @@
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
.grid-item {
height: 200px;
}
</style>
</head>
<body>
<ion-app>
<div class="ion-page">
<ion-header collapse="fade" translucent="true">
<div style="position: absolute; left: 0; right: 0; bottom: 0">
<img style="transform: rotate(180deg)" src="/src/components/header/test/img.jpg" />
</div>
<ion-toolbar>
<ion-title>Mailboxes</ion-title>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content fullscreen="true">
<div class="grid ion-padding">
<div class="grid-item red"></div>
<div class="grid-item green"></div>
<div class="grid-item blue"></div>
<div class="grid-item yellow"></div>
<div class="grid-item pink"></div>
<div class="grid-item purple"></div>
<div class="grid-item black"></div>
<div class="grid-item orange"></div>
<div class="ion-padding">
<h1>Content</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit, justo vitae rhoncus porttitor,
massa elit molestie nisl, ut tincidunt orci enim ac ante. Vestibulum tincidunt dignissim elit. Morbi
cursus hendrerit turpis, ut egestas tortor pulvinar sit amet. Interdum et malesuada fames ac ante ipsum
primis in faucibus. Phasellus faucibus consequat purus vel mollis. Ut ultricies elit nunc. Quisque
ultrices turpis vel augue auctor accumsan. Donec bibendum at nisi vel finibus. Fusce id imperdiet odio.
Morbi orci ipsum, imperdiet ut iaculis sit amet, suscipit vulputate felis. Nullam arcu leo, volutpat eu
porttitor sed, fringilla et arcu. Pellentesque ac libero sapien. Quisque id dui velit. Mauris et pretium
tortor.
</p>
<p>
Ut ultricies id augue vel aliquam. Etiam ornare finibus nisl, nec egestas urna. Nam pellentesque libero
nec justo tristique lacinia. In sit amet gravida metus, ac tincidunt mauris. Fusce sit amet tempus turpis.
Nulla ligula nunc, vestibulum quis quam in, feugiat aliquet nibh. Quisque in ante non nulla luctus gravida
vel at lacus. Vivamus erat magna, elementum et dignissim nec, posuere eu ante. Praesent elementum, arcu
scelerisque venenatis sodales, turpis nulla aliquam urna, id hendrerit est orci et purus. Duis sem ipsum,
imperdiet eu elit id, tincidunt tempus sapien. Praesent tincidunt, sapien sed rhoncus euismod, lectus
velit ornare nunc, dapibus varius turpis leo ut magna.
</p>
<p>
Nam quis quam id ante mattis pulvinar non sed mauris. Donec tempor sed nulla at semper. Vivamus ac nunc
bibendum, ullamcorper lacus quis, ornare massa. Cras gravida nibh risus, id sollicitudin eros ultricies
non. Integer velit massa, suscipit tincidunt rhoncus ut, lacinia et nisl. Maecenas volutpat ipsum blandit
sollicitudin lobortis. Suspendisse potenti. Cras non mi non arcu varius dapibus. Suspendisse maximus eget
justo a lobortis. Donec nulla ipsum, efficitur eget velit eget, varius rutrum quam. Nulla metus risus,
accumsan a tellus ac, faucibus blandit quam. Donec luctus, nisl ac ultricies ornare, nunc elit finibus
magna, id elementum ante urna congue ex. Cras condimentum nisi sollicitudin tortor vestibulum luctus.
Curabitur non ipsum et ex vestibulum congue.
</p>
<p>
Maecenas rhoncus elit ut consectetur faucibus. Etiam sed sem sed mauris condimentum viverra sit amet at
nibh. Mauris bibendum at purus a cursus. Suspendisse potenti. Donec vel lacus ac odio euismod lacinia id
in urna. Donec commodo ipsum augue, at bibendum ex convallis suscipit. Nulla ac rhoncus odio. Aenean
elementum est nec arcu ultricies dignissim.
</p>
<p>
Sed tincidunt bibendum massa, egestas bibendum est imperdiet vitae. Fusce dignissim consectetur ante a
fermentum. Morbi suscipit turpis sapien. Suspendisse eleifend sapien eget nunc mattis mattis. Phasellus
rhoncus sodales libero a imperdiet. Nam in vulputate lectus. Proin accumsan enim non nibh sagittis
ultricies. Nullam vitae ultricies nunc. Nullam ultrices dolor nec vehicula posuere.
</p>
</div>
</ion-content>
<ion-footer translucent="true">
<ion-toolbar>
<ion-title>Updated Just Now</ion-title>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -1,51 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
import type { E2EPage } from '@stencil/core/testing';
import { scrollToBottom } from '@utils/test';
describe('ion-header: custom scroll target', () => {
let page: E2EPage;
beforeEach(async () => {
page = await newE2EPage({
url: '/src/components/header/test/scroll-target?ionic:_testing=true&ionic:mode=ios',
});
});
it('should match existing visual screenshots', async () => {
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});
describe('large title', () => {
it('should display the large title initially', async () => {
const largeHeader = await page.find('ion-header[collapse="condense"]');
const collapseHeader = await page.find('ion-header[collapse="fade"]');
expect(largeHeader.className).not.toContain('header-collapse-condense-inactive');
expect(collapseHeader.className).toContain('header-collapse-condense-inactive');
});
describe('when the scroll container has overflow', () => {
it('should display the collapsed title on scroll', async () => {
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot('large title expanded'));
const largeHeader = await page.find('ion-header[collapse="condense"]');
const collapseHeader = await page.find('ion-header[collapse="fade"]');
await scrollToBottom(page, '#scroll-target');
await page.waitForChanges();
expect(largeHeader.className).toContain('header-collapse-condense-inactive');
expect(collapseHeader.className).not.toContain('header-collapse-condense-inactive');
screenshotCompares.push(await page.compareScreenshot('large title collapsed'));
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
});
});
});

View File

@ -0,0 +1,33 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('header: scroll-target', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
});
/**
* This test suite verifies that the fade effect for iOS is working correctly
* when the `ion-header` is using a custom scroll target with the `.ion-content-scroll-host`
* selector.
*/
test('should not have visual regressions with custom scroll target header', async ({ page, skip }) => {
skip.mode('md', 'Translucent effect is only available in iOS mode.');
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
await page.goto('/src/components/header/test/scroll-target');
const header = page.locator('ion-header');
expect(await header.screenshot({ animations: 'disabled' })).toMatchSnapshot(
`header-scroll-target-not-blurred-diff-${page.getSnapshotSettings()}.png`
);
const scrollTarget = page.locator('#scroll-target');
await scrollTarget.evaluate((el: HTMLDivElement) => (el.scrollTop = el.scrollHeight));
await page.waitForChanges();
expect(await header.screenshot({ animations: 'disabled' })).toMatchSnapshot(
`header-scroll-target-blurred-diff-${page.getSnapshotSettings()}.png`
);
});
});

View File

@ -13,48 +13,6 @@
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
.grid-item {
height: 200px;
}
#scroll-target {
position: absolute;
top: 0;
@ -73,32 +31,63 @@
<ion-app>
<div class="ion-page">
<ion-header collapse="fade" translucent="true">
<div style="position: absolute; left: 0; right: 0; bottom: 0">
<img style="transform: rotate(180deg)" src="/src/components/header/test/img.jpg" />
</div>
<ion-toolbar>
<ion-title>Mailboxes</ion-title>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content fullscreen="true" scroll-y="false">
<div id="scroll-target" class="ion-content-scroll-host">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Mailboxes</ion-title>
</ion-toolbar>
</ion-header>
<div class="grid ion-padding">
<div class="grid-item red"></div>
<div class="grid-item green"></div>
<div class="grid-item blue"></div>
<div class="grid-item yellow"></div>
<div class="grid-item pink"></div>
<div class="grid-item purple"></div>
<div class="grid-item black"></div>
<div class="grid-item orange"></div>
</div>
<h1>Content</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit, justo vitae rhoncus porttitor,
massa elit molestie nisl, ut tincidunt orci enim ac ante. Vestibulum tincidunt dignissim elit. Morbi
cursus hendrerit turpis, ut egestas tortor pulvinar sit amet. Interdum et malesuada fames ac ante ipsum
primis in faucibus. Phasellus faucibus consequat purus vel mollis. Ut ultricies elit nunc. Quisque
ultrices turpis vel augue auctor accumsan. Donec bibendum at nisi vel finibus. Fusce id imperdiet odio.
Morbi orci ipsum, imperdiet ut iaculis sit amet, suscipit vulputate felis. Nullam arcu leo, volutpat eu
porttitor sed, fringilla et arcu. Pellentesque ac libero sapien. Quisque id dui velit. Mauris et pretium
tortor.
</p>
<p>
Ut ultricies id augue vel aliquam. Etiam ornare finibus nisl, nec egestas urna. Nam pellentesque libero
nec justo tristique lacinia. In sit amet gravida metus, ac tincidunt mauris. Fusce sit amet tempus turpis.
Nulla ligula nunc, vestibulum quis quam in, feugiat aliquet nibh. Quisque in ante non nulla luctus gravida
vel at lacus. Vivamus erat magna, elementum et dignissim nec, posuere eu ante. Praesent elementum, arcu
scelerisque venenatis sodales, turpis nulla aliquam urna, id hendrerit est orci et purus. Duis sem ipsum,
imperdiet eu elit id, tincidunt tempus sapien. Praesent tincidunt, sapien sed rhoncus euismod, lectus
velit ornare nunc, dapibus varius turpis leo ut magna.
</p>
<p>
Nam quis quam id ante mattis pulvinar non sed mauris. Donec tempor sed nulla at semper. Vivamus ac nunc
bibendum, ullamcorper lacus quis, ornare massa. Cras gravida nibh risus, id sollicitudin eros ultricies
non. Integer velit massa, suscipit tincidunt rhoncus ut, lacinia et nisl. Maecenas volutpat ipsum blandit
sollicitudin lobortis. Suspendisse potenti. Cras non mi non arcu varius dapibus. Suspendisse maximus eget
justo a lobortis. Donec nulla ipsum, efficitur eget velit eget, varius rutrum quam. Nulla metus risus,
accumsan a tellus ac, faucibus blandit quam. Donec luctus, nisl ac ultricies ornare, nunc elit finibus
magna, id elementum ante urna congue ex. Cras condimentum nisi sollicitudin tortor vestibulum luctus.
Curabitur non ipsum et ex vestibulum congue.
</p>
<p>
Maecenas rhoncus elit ut consectetur faucibus. Etiam sed sem sed mauris condimentum viverra sit amet at
nibh. Mauris bibendum at purus a cursus. Suspendisse potenti. Donec vel lacus ac odio euismod lacinia id
in urna. Donec commodo ipsum augue, at bibendum ex convallis suscipit. Nulla ac rhoncus odio. Aenean
elementum est nec arcu ultricies dignissim.
</p>
<p>
Sed tincidunt bibendum massa, egestas bibendum est imperdiet vitae. Fusce dignissim consectetur ante a
fermentum. Morbi suscipit turpis sapien. Suspendisse eleifend sapien eget nunc mattis mattis. Phasellus
rhoncus sodales libero a imperdiet. Nam in vulputate lectus. Proin accumsan enim non nibh sagittis
ultricies. Nullam vitae ultricies nunc. Nullam ultrices dolor nec vehicula posuere.
</p>
</div>
</ion-content>
<ion-footer translucent="true">
<ion-toolbar>
<ion-title>Updated Just Now</ion-title>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</div>

View File

@ -1,14 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
import { checkComponentModeClasses } from '@utils/test';
test('header: translucent', async () => {
const page = await newE2EPage({
url: '/src/components/header/test/translucent?ionic:_testing=true',
});
const globalMode = await page.evaluate(() => document.documentElement.getAttribute('mode'));
await checkComponentModeClasses(await page.find('ion-header'), globalMode!, 'header-translucent');
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -1,170 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Header - Translucent</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header translucent>
<ion-toolbar>
<ion-title>Header - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="primary">
<ion-title>Primary - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="secondary">
<ion-title>Secondary - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="tertiary">
<ion-title>Tertiary - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="success">
<ion-title>Success - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="warning">
<ion-title>Warning - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="danger">
<ion-title>Danger - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="light">
<ion-title>Light - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="medium">
<ion-title>Medium - Translucent</ion-title>
</ion-toolbar>
</ion-header>
<ion-content fullscreen>
<ion-grid>
<ion-row>
<ion-col size="6">
<f class="red"></f>
</ion-col>
<ion-col size="6">
<f class="green"></f>
</ion-col>
<ion-col size="6">
<f class="blue"></f>
</ion-col>
<ion-col size="6">
<f class="yellow"></f>
</ion-col>
<ion-col size="6">
<f class="pink"></f>
</ion-col>
<ion-col size="6">
<f class="purple"></f>
</ion-col>
<ion-col size="6">
<f class="black"></f>
</ion-col>
<ion-col size="6">
<f class="orange"></f>
</ion-col>
</ion-row>
</ion-grid>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
scelerisque nisl eu suscipit consectetur.
</p>
<ion-grid>
<ion-row>
<ion-col size="6">
<f class="red"></f>
</ion-col>
<ion-col size="6">
<f class="green"></f>
</ion-col>
<ion-col size="6">
<f class="blue"></f>
</ion-col>
<ion-col size="6">
<f class="yellow"></f>
</ion-col>
<ion-col size="6">
<f class="pink"></f>
</ion-col>
<ion-col size="6">
<f class="purple"></f>
</ion-col>
<ion-col size="6">
<f class="black"></f>
</ion-col>
<ion-col size="6">
<f class="orange"></f>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
</ion-app>
<style>
f {
display: block;
height: 200px;
}
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
</style>
</body>
</html>

View File

@ -1,9 +0,0 @@
// Item Mixins
// --------------------------------------------------
@mixin item-push-svg-url($fill) {
$item-detail-push-svg: "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 20'><path d='M2,20l-2-2l8-8L0,2l2-2l10,10L2,20z' fill='#{$fill}'/></svg>";
@include svg-background-image($item-detail-push-svg, true);
}

View File

@ -1,5 +1,4 @@
@import "../../themes/ionic.globals";
@import "./item.mixins";
// Item
// --------------------------------------------------

View File

@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('list: basic', async () => {
const page = await newE2EPage({
url: '/src/components/list/test/basic?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -28,19 +28,6 @@
<ion-item>Super Metroid</ion-item>
<ion-item>Mega Man X</ion-item>
<ion-item>The Legend of Zelda</ion-item>
<ion-item>Pac-Man</ion-item>
<ion-item>Super Mario World</ion-item>
<ion-item>Street Fighter II</ion-item>
<ion-item>Half Life</ion-item>
<ion-item>Portal</ion-item>
<ion-item>Final Fantasy VII</ion-item>
<ion-item>Star Fox</ion-item>
<ion-item>Tetris</ion-item>
<ion-item>Donkey Kong III</ion-item>
<ion-item>Goldeneye 007</ion-item>
<ion-item>Doom</ion-item>
<ion-item>Fallout</ion-item>
<ion-item>GTA</ion-item>
<ion-item lines="full">Halo</ion-item>
</ion-list>
</ion-content>

View File

@ -0,0 +1,12 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('list: basic', () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/list/test/basic`);
const list = page.locator('ion-list');
expect(await list.screenshot()).toMatchSnapshot(`list-basic-diff-${page.getSnapshotSettings()}.png`);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('list: icons', async () => {
const page = await newE2EPage({
url: '/src/components/list/test/icons?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -1,93 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>List - Icons</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Trip</ion-title>
<ion-buttons slot="end">
<ion-button>
<ion-icon slot="icon-only" name="search-outline"></ion-icon>
</ion-button>
<ion-button>
<ion-icon slot="icon-only" ios="ellipsis-horizontal" md="ellipsis-vertical"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-list-header lines="full">
<ion-label> Categories </ion-label>
</ion-list-header>
<ion-item button>
<ion-icon name="film-outline" slot="start"></ion-icon>
<ion-label> Attractions </ion-label>
</ion-item>
<ion-item button>
<ion-icon name="restaurant-outline" slot="start"></ion-icon>
<ion-label> Dining </ion-label>
</ion-item>
<ion-item button>
<ion-icon name="create-outline" slot="start"></ion-icon>
<ion-label> Education </ion-label>
</ion-item>
<ion-item button lines="full">
<ion-icon name="airplane-outline" slot="start"></ion-icon>
<ion-label> Travel </ion-label>
</ion-item>
</ion-list>
<ion-list>
<ion-list-header lines="full">
<ion-label> Information </ion-label>
</ion-list-header>
<ion-item>
<ion-icon name="people-outline" slot="start"></ion-icon>
<ion-label> Attendees </ion-label>
<ion-select slot="end" value="1">
<ion-select-option value="1">1</ion-select-option>
<ion-select-option value="2">2</ion-select-option>
<ion-select-option value="3">3</ion-select-option>
</ion-select>
</ion-item>
<ion-item lines="full">
<ion-icon name="repeat-outline" slot="start"></ion-icon>
<ion-label> Round-trip </ion-label>
<ion-toggle checked></ion-toggle>
</ion-item>
</ion-list>
<ion-list>
<ion-list-header lines="full">
<ion-label> Contact </ion-label>
</ion-list-header>
<ion-item>
<ion-icon name="person-outline" slot="start"></ion-icon>
<ion-label> Name </ion-label>
<ion-input placeholder="xxxxx"></ion-input>
</ion-item>
<ion-item lines="full">
<ion-icon name="call-outline" slot="start"></ion-icon>
<ion-label> Phone Number </ion-label>
<ion-input placeholder="(xxx) xxx-xxxx" type="number"></ion-input>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('list: lines', async () => {
const page = await newE2EPage({
url: '/src/components/list/test/lines?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -2,7 +2,7 @@
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>List - Basic</title>
<title>List - Lines</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
@ -18,7 +18,7 @@
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>List - Basic</ion-title>
<ion-title>List - Lines</ion-title>
</ion-toolbar>
</ion-header>
@ -53,80 +53,6 @@
<ion-item><ion-label>Donkey Kong III</ion-label></ion-item>
<ion-item><ion-label>Goldeneye 007</ion-label></ion-item>
</ion-list>
<ion-list>
<ion-list-header>
<ion-label> Lines: Default </ion-label>
</ion-list-header>
<ion-item><ion-label>Fallout</ion-label></ion-item>
<ion-item><ion-label>GTA</ion-label></ion-item>
<ion-item><ion-label>Halo</ion-label></ion-item>
<ion-item><ion-label>Doom</ion-label></ion-item>
<ion-item><ion-label>Final Fantasy VII</ion-label></ion-item>
</ion-list>
<ion-list>
<ion-list-header>
<ion-label> Wrapper item: default </ion-label>
</ion-list-header>
<div>
<ion-item><ion-label>Pokémon Yellow</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>Super Metroid</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>Mega Man X</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>The Legend of Zelda</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>Pac-Man</ion-label></ion-item>
</div>
</ion-list>
<ion-list lines="none">
<ion-list-header>
<ion-label> Wrapper list lines: none </ion-label>
</ion-list-header>
<div>
<ion-item><ion-label>Pokémon Yellow</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>Super Metroid</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>Mega Man X</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>The Legend of Zelda</ion-label></ion-item>
</div>
<div>
<ion-item><ion-label>Pac-Man</ion-label></ion-item>
</div>
</ion-list>
<ion-list>
<ion-list-header>
<ion-label> Wrapper w/ item lines: none </ion-label>
</ion-list-header>
<div>
<ion-item lines="none"><ion-label>Pokémon Yellow</ion-label></ion-item>
</div>
<div>
<ion-item lines="none"><ion-label>Super Metroid</ion-label></ion-item>
</div>
<div>
<ion-item lines="none"><ion-label>Mega Man X</ion-label></ion-item>
</div>
<div>
<ion-item lines="none"><ion-label>The Legend of Zelda</ion-label></ion-item>
</div>
<div>
<ion-item lines="none"><ion-label>Pac-Man</ion-label></ion-item>
</div>
</ion-list>
</ion-content>
</ion-app>
</body>

View File

@ -0,0 +1,26 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('list: lines', () => {
test('lines="full" should render correctly', async ({ page }) => {
await page.goto(`/src/components/list/test/lines`);
const list = page.locator('ion-list[lines="full"]');
expect(await list.screenshot()).toMatchSnapshot(`list-lines-full-${page.getSnapshotSettings()}.png`);
});
test('lines="inset" should render correctly', async ({ page }) => {
await page.goto(`/src/components/list/test/lines`);
const list = page.locator('ion-list[lines="inset"]');
expect(await list.screenshot()).toMatchSnapshot(`list-lines-inset-${page.getSnapshotSettings()}.png`);
});
test('lines="none" should render correctly', async ({ page }) => {
await page.goto(`/src/components/list/test/lines`);
const list = page.locator('ion-list[lines="none"]');
expect(await list.screenshot()).toMatchSnapshot(`list-lines-none-${page.getSnapshotSettings()}.png`);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Some files were not shown because too many files have changed in this diff Show More