Skip to content

Commit 755d882

Browse files
ariellalgilmoretay1orjonesheloiseluiannawen1
authored
feat(page-header): add content implementation (#18930)
* feat(pageheader): initial components, exports, stories, styles, tests * chore: update copyright, remove old component files * chore: ignore stylelint * chore: update snaps * feat(page-header-content): component * fix(spacing): pageheadercontent * fix(title): add ellipsis update * fix(pageHeaderContent): cleanup * feat(pageheader): add hero image component * chore: add another image * fix(export and image): update * fix(snapshots): config jest to use prettier2 * test(content): add coverage * test(contentt): add avt/vrt test * chore(page-header-content): additional cleanup * fix(page-header): cleanup styles * chore: center hero image * fix(content): cleanup * fix(api): update --------- Co-authored-by: Taylor Jones <taylor.jones826@gmail.com> Co-authored-by: Taylor Jones <tay1orjones@users.noreply.github.com> Co-authored-by: Heloise Lui <71858203+heloiselui@users.noreply.github.com> Co-authored-by: Anna Wen <54281166+annawen1@users.noreply.github.com>
1 parent a652197 commit 755d882

File tree

11 files changed

+838
-14
lines changed

11 files changed

+838
-14
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2023
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
import { expect, test } from '@playwright/test';
10+
import { visitStory } from '../../test-utils/storybook';
11+
12+
test.describe('@avt PageHeader', () => {
13+
test('@avt-default-state content', async ({ page }) => {
14+
await visitStory(page, {
15+
component: 'PageHeader.Content',
16+
id: 'patterns-unstable-pageheader--content',
17+
globals: {
18+
theme: 'white',
19+
},
20+
});
21+
await expect(page).toHaveNoACViolations('PageHeader.Content');
22+
});
23+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2023
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
const { test } = require('@playwright/test');
10+
const { themes } = require('../../test-utils/env');
11+
const { snapshot } = require('../../test-utils/snapshot');
12+
const { snapshotStory, visitStory } = require('../../test-utils/storybook');
13+
14+
test.describe('PageHeader', () => {
15+
themes.forEach((theme) => {
16+
test.describe(theme, () => {
17+
test('page header @vrt', async ({ page }) => {
18+
await snapshotStory(page, {
19+
component: 'PageHeader',
20+
id: 'patterns-unstable-pageheader--content',
21+
theme,
22+
});
23+
});
24+
test('page header with contextual actions @vrt', async ({ page }) => {
25+
await snapshotStory(page, {
26+
component: 'PageHeader',
27+
id: 'patterns-unstable-pageheader--content-with-contextual-actions',
28+
theme,
29+
});
30+
});
31+
test('page header with contextual actions and page actions @vrt', async ({
32+
page,
33+
}) => {
34+
await snapshotStory(page, {
35+
component: 'PageHeader',
36+
id: 'patterns-unstable-pageheader--content-with-contextual-actions-and-page-actions',
37+
theme,
38+
});
39+
});
40+
test('page header with hero image @vrt', async ({ page }) => {
41+
await snapshotStory(page, {
42+
component: 'PageHeader',
43+
id: 'patterns-unstable-pageheader--content-with-hero-image',
44+
theme,
45+
});
46+
});
47+
test('page header with icon @vrt', async ({ page }) => {
48+
await snapshotStory(page, {
49+
component: 'PageHeader',
50+
id: 'patterns-unstable-pageheader--content-with-icon',
51+
theme,
52+
});
53+
});
54+
});
55+
});
56+
});

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10898,6 +10898,52 @@ Map {
1089810898
},
1089910899
"Content": Object {
1090010900
"$$typeof": Symbol(react.forward_ref),
10901+
"propTypes": Object {
10902+
"children": Object {
10903+
"type": "node",
10904+
},
10905+
"className": Object {
10906+
"type": "string",
10907+
},
10908+
"contextualActions": Object {
10909+
"type": "node",
10910+
},
10911+
"pageActions": Object {
10912+
"type": "node",
10913+
},
10914+
"renderIcon": Object {
10915+
"args": Array [
10916+
Array [
10917+
Object {
10918+
"type": "func",
10919+
},
10920+
Object {
10921+
"type": "object",
10922+
},
10923+
],
10924+
],
10925+
"type": "oneOfType",
10926+
},
10927+
"subtitle": Object {
10928+
"type": "string",
10929+
},
10930+
"title": Object {
10931+
"isRequired": true,
10932+
"type": "string",
10933+
},
10934+
},
10935+
"render": [Function],
10936+
},
10937+
"HeroImage": Object {
10938+
"$$typeof": Symbol(react.forward_ref),
10939+
"propTypes": Object {
10940+
"children": Object {
10941+
"type": "node",
10942+
},
10943+
"className": Object {
10944+
"type": "string",
10945+
},
10946+
},
1090110947
"render": [Function],
1090210948
},
1090310949
"PageHeader": Object {
@@ -10910,6 +10956,52 @@ Map {
1091010956
},
1091110957
"PageHeaderContent": Object {
1091210958
"$$typeof": Symbol(react.forward_ref),
10959+
"propTypes": Object {
10960+
"children": Object {
10961+
"type": "node",
10962+
},
10963+
"className": Object {
10964+
"type": "string",
10965+
},
10966+
"contextualActions": Object {
10967+
"type": "node",
10968+
},
10969+
"pageActions": Object {
10970+
"type": "node",
10971+
},
10972+
"renderIcon": Object {
10973+
"args": Array [
10974+
Array [
10975+
Object {
10976+
"type": "func",
10977+
},
10978+
Object {
10979+
"type": "object",
10980+
},
10981+
],
10982+
],
10983+
"type": "oneOfType",
10984+
},
10985+
"subtitle": Object {
10986+
"type": "string",
10987+
},
10988+
"title": Object {
10989+
"isRequired": true,
10990+
"type": "string",
10991+
},
10992+
},
10993+
"render": [Function],
10994+
},
10995+
"PageHeaderHeroImage": Object {
10996+
"$$typeof": Symbol(react.forward_ref),
10997+
"propTypes": Object {
10998+
"children": Object {
10999+
"type": "node",
11000+
},
11001+
"className": Object {
11002+
"type": "string",
11003+
},
11004+
},
1091311005
"render": [Function],
1091411006
},
1091511007
"PageHeaderTabBar": Object {

packages/react/src/components/PageHeader/PageHeader-test.js

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ import {
1414
PageHeaderContent as PageHeaderContentDirect,
1515
PageHeaderTabBar as PageHeaderTabBarDirect,
1616
} from '../PageHeader';
17+
import * as hooks from '../../internal/useMatchMedia';
18+
import { breakpoints } from '@carbon/layout';
19+
20+
import { Bee } from '@carbon/icons-react';
21+
22+
const prefix = 'cds';
1723

1824
describe('PageHeader', () => {
1925
describe('export configuration', () => {
2026
it('supports dot notation component namespacing from the main entrypoint', () => {
2127
const { container } = render(
2228
<PageHeader.Root>
2329
<PageHeader.BreadcrumbBar />
24-
<PageHeader.Content />
30+
<PageHeader.Content title="title" />
2531
<PageHeader.TabBar />
2632
</PageHeader.Root>
2733
);
@@ -32,7 +38,7 @@ describe('PageHeader', () => {
3238
const { container } = render(
3339
<PageHeaderDirect>
3440
<PageHeaderBreadcrumbBarDirect />
35-
<PageHeaderContentDirect />
41+
<PageHeaderContentDirect title="title" />
3642
<PageHeaderTabBarDirect />
3743
</PageHeaderDirect>
3844
);
@@ -70,16 +76,153 @@ describe('PageHeader', () => {
7076

7177
describe('PageHeader.Content component api', () => {
7278
it('should render', () => {
73-
const { container } = render(<PageHeader.Content />);
79+
const { container } = render(<PageHeader.Content title="title" />);
7480
expect(container.firstChild).toBeInTheDocument();
7581
});
7682

7783
it('should place className on the outermost element', () => {
7884
const { container } = render(
79-
<PageHeader.Content className="custom-class" />
85+
<PageHeader.Content className="custom-class" title="title" />
86+
);
87+
expect(container.firstChild).toHaveClass('custom-class');
88+
});
89+
90+
it('should render a title', () => {
91+
render(<PageHeader.Content title="Page header content title" />);
92+
93+
expect(screen.getByText('Page header content title')).toBeInTheDocument();
94+
});
95+
96+
it('should render an icon', () => {
97+
const { container } = render(
98+
<PageHeader.Content
99+
title="title"
100+
renderIcon={() => {
101+
return <Bee size={32} />;
102+
}}></PageHeader.Content>
103+
);
104+
105+
const icon = container.querySelector(
106+
`.${prefix}--page-header__content__icon`
107+
);
108+
expect(icon).toBeInTheDocument();
109+
});
110+
111+
it('should render a subtitle', () => {
112+
render(<PageHeader.Content title="title" subtitle="subtitle" />);
113+
114+
expect(screen.getByText('subtitle')).toBeInTheDocument();
115+
});
116+
117+
it('should render children', () => {
118+
render(
119+
<PageHeader.Content title="title">Children content</PageHeader.Content>
120+
);
121+
122+
expect(screen.getByText('Children content')).toBeInTheDocument();
123+
});
124+
125+
it('should render contextual actions', () => {
126+
const { container } = render(
127+
<PageHeader.Content
128+
title="title"
129+
contextualActions={
130+
<>
131+
<div>action 1</div>
132+
<div>action 2</div>
133+
<div>action 3</div>
134+
</>
135+
}></PageHeader.Content>
136+
);
137+
138+
const pageActions = container.querySelector(
139+
`.${prefix}--page-header__content__contextual-actions`
140+
);
141+
expect(pageActions).toBeInTheDocument();
142+
});
143+
144+
it('should render page actions', () => {
145+
const { container } = render(
146+
<PageHeader.Content
147+
title="title"
148+
pageActions={
149+
<>
150+
<div>action 1</div>
151+
<div>action 2</div>
152+
<div>action 3</div>
153+
</>
154+
}></PageHeader.Content>
155+
);
156+
157+
const pageActions = container.querySelector(
158+
`.${prefix}--page-header__content__page-actions`
159+
);
160+
expect(pageActions).toBeInTheDocument();
161+
});
162+
});
163+
164+
describe('PageHeader.HeroImage component api', () => {
165+
beforeEach(() => {
166+
jest.resetModules();
167+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
168+
});
169+
170+
it('should place className on the outermost element', () => {
171+
const { container } = render(
172+
<PageHeader.HeroImage className="custom-class" />
80173
);
81174
expect(container.firstChild).toHaveClass('custom-class');
82175
});
176+
177+
it('should use a 2x1 ratio on large screens', () => {
178+
const { container } = render(
179+
<PageHeader.HeroImage>
180+
<picture>
181+
<source
182+
srcSet="https://picsum.photos/200/100"
183+
media={`(min-width: ${breakpoints.lg.width}`}
184+
/>
185+
<source
186+
srcSet="https://picsum.photos/300/200"
187+
media={`(max-width: ${breakpoints.lg.width}`}
188+
/>
189+
<img
190+
src="https://picsum.photos/200/100"
191+
alt="a default image"
192+
style={{ maxWidth: '100%', height: 'auto' }}
193+
/>
194+
</picture>
195+
</PageHeader.HeroImage>
196+
);
197+
198+
expect(container.firstChild).toHaveClass(`${prefix}--aspect-ratio--2x1`);
199+
});
200+
201+
it('should use a 3x2 ratio on small screens', () => {
202+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => false);
203+
204+
const { container } = render(
205+
<PageHeader.HeroImage>
206+
<picture>
207+
<source
208+
srcSet="https://picsum.photos/200/100"
209+
media={`(min-width: ${breakpoints.lg.width}`}
210+
/>
211+
<source
212+
srcSet="https://picsum.photos/300/200"
213+
media={`(max-width: ${breakpoints.lg.width}`}
214+
/>
215+
<img
216+
src="https://picsum.photos/200/100"
217+
alt="a default image"
218+
style={{ maxWidth: '100%', height: 'auto' }}
219+
/>
220+
</picture>
221+
</PageHeader.HeroImage>
222+
);
223+
224+
expect(container.firstChild).toHaveClass(`${prefix}--aspect-ratio--3x2`);
225+
});
83226
});
84227

85228
describe('PageHeader.TabBar component api', () => {

0 commit comments

Comments
 (0)