@@ -5,6 +5,12 @@ import { configs, test } from '@utils/test/playwright';
55 * Safe-area tests verify that ion-content correctly applies safe-area classes
66 * based on the presence/absence of sibling ion-header and ion-footer elements.
77 *
8+ * Safe-area class logic:
9+ * - safe-area-top: main content without header
10+ * - safe-area-bottom: main content without footer/tab-bar
11+ * - safe-area-left: always on main content (for landscape notched devices)
12+ * - safe-area-right: always on main content (for landscape notched devices)
13+ *
814 * These tests verify the FW-6830 feature: automatic safe-area handling for content.
915 */
1016
@@ -23,6 +29,9 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
2329 const content = page . locator ( '#content-no-header' ) ;
2430 await expect ( content ) . toHaveClass ( / s a f e - a r e a - t o p / ) ;
2531 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
32+ // Left/right always apply to main content
33+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
34+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
2635 } ) ;
2736
2837 test ( 'content without footer should have safe-area-bottom class' , async ( { page } , testInfo ) => {
@@ -34,9 +43,12 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
3443 const content = page . locator ( '#content-no-footer' ) ;
3544 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - t o p / ) ;
3645 await expect ( content ) . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
46+ // Left/right always apply to main content
47+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
48+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
3749 } ) ;
3850
39- test ( 'content with both header and footer should not have safe-area classes' , async ( { page } , testInfo ) => {
51+ test ( 'content with both header and footer should not have top/bottom safe-area classes' , async ( { page } , testInfo ) => {
4052 testInfo . annotations . push ( {
4153 type : 'issue' ,
4254 description : 'https://outsystemsrd.atlassian.net/browse/FW-6830' ,
@@ -45,9 +57,12 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
4557 const content = page . locator ( '#content-with-both' ) ;
4658 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - t o p / ) ;
4759 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
60+ // Left/right still apply to main content even with header/footer
61+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
62+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
4863 } ) ;
4964
50- test ( 'content without header or footer should have both safe-area classes' , async ( { page } , testInfo ) => {
65+ test ( 'content without header or footer should have all safe-area classes' , async ( { page } , testInfo ) => {
5166 testInfo . annotations . push ( {
5267 type : 'issue' ,
5368 description : 'https://outsystemsrd.atlassian.net/browse/FW-6830' ,
@@ -56,6 +71,8 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
5671 const content = page . locator ( '#content-no-both' ) ;
5772 await expect ( content ) . toHaveClass ( / s a f e - a r e a - t o p / ) ;
5873 await expect ( content ) . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
74+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
75+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
5976 } ) ;
6077
6178 test ( 'content with wrapped header should not have safe-area-top class' , async ( { page } , testInfo ) => {
@@ -67,6 +84,9 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
6784 const content = page . locator ( '#content-wrapped-header' ) ;
6885 // Wrapped header detection should find the ion-header inside my-header
6986 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - t o p / ) ;
87+ // Left/right still apply to main content
88+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
89+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
7090 } ) ;
7191
7292 test ( 'content with wrapped footer should not have safe-area-bottom class' , async ( { page } , testInfo ) => {
@@ -78,33 +98,40 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
7898 const content = page . locator ( '#content-wrapped-footer' ) ;
7999 // Wrapped footer detection should find the ion-footer inside my-footer
80100 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
101+ // Left/right still apply to main content
102+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
103+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
81104 } ) ;
82105
83- test ( 'nested content should not have safe-area classes' , async ( { page } , testInfo ) => {
106+ test ( 'nested content should not have any safe-area classes' , async ( { page } , testInfo ) => {
84107 testInfo . annotations . push ( {
85108 type : 'issue' ,
86109 description : 'https://outsystemsrd.atlassian.net/browse/FW-6830' ,
87110 } ) ;
88111
89112 const nestedContent = page . locator ( '#content-nested' ) ;
90- // Nested content should not be treated as main content
113+ // Nested content should not be treated as main content - no safe-area classes at all
91114 await expect ( nestedContent ) . not . toHaveClass ( / s a f e - a r e a - t o p / ) ;
92115 await expect ( nestedContent ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
116+ await expect ( nestedContent ) . not . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
117+ await expect ( nestedContent ) . not . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
93118 } ) ;
94119
95- test ( 'outer content should still have safe-area classes' , async ( { page } , testInfo ) => {
120+ test ( 'outer content should have all safe-area classes' , async ( { page } , testInfo ) => {
96121 testInfo . annotations . push ( {
97122 type : 'issue' ,
98123 description : 'https://outsystemsrd.atlassian.net/browse/FW-6830' ,
99124 } ) ;
100125
101126 const outerContent = page . locator ( '#content-outer' ) ;
102- // Outer content has no sibling header/footer, so it should have safe-area classes
127+ // Outer content has no sibling header/footer, so it should have all safe-area classes
103128 await expect ( outerContent ) . toHaveClass ( / s a f e - a r e a - t o p / ) ;
104129 await expect ( outerContent ) . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
130+ await expect ( outerContent ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
131+ await expect ( outerContent ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
105132 } ) ;
106133
107- test ( 'content inside modal should not have safe-area classes' , async ( { page } , testInfo ) => {
134+ test ( 'content inside modal should not have any safe-area classes' , async ( { page } , testInfo ) => {
108135 testInfo . annotations . push ( {
109136 type : 'issue' ,
110137 description : 'https://outsystemsrd.atlassian.net/browse/FW-6830' ,
@@ -123,9 +150,11 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
123150 await ionModalDidPresent . next ( ) ;
124151
125152 const modalContent = page . locator ( '#content-in-modal' ) ;
126- // Content inside modal should not be treated as main content
153+ // Content inside modal should not be treated as main content - no safe-area classes at all
127154 await expect ( modalContent ) . not . toHaveClass ( / s a f e - a r e a - t o p / ) ;
128155 await expect ( modalContent ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
156+ await expect ( modalContent ) . not . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
157+ await expect ( modalContent ) . not . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
129158 } ) ;
130159
131160 test ( 'dynamic header addition should update safe-area classes' , async ( { page } , testInfo ) => {
@@ -136,14 +165,19 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
136165
137166 const content = page . locator ( '#content-dynamic' ) ;
138167
139- // Initially should have safe-area-top (no header)
168+ // Initially should have safe-area-top (no header) and left/right (always on main content)
140169 await expect ( content ) . toHaveClass ( / s a f e - a r e a - t o p / ) ;
170+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
171+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
141172
142173 // Add header dynamically (use evaluate to avoid pointer-events issues in Firefox)
143174 await page . evaluate ( ( ) => ( window as any ) . addHeader ( ) ) ;
144175
145176 // Wait for mutation observer to trigger and component to update
146177 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - t o p / , { timeout : 1000 } ) ;
178+ // Left/right should remain
179+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
180+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
147181 } ) ;
148182
149183 test ( 'dynamic header removal should update safe-area classes' , async ( { page } , testInfo ) => {
@@ -157,12 +191,17 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
157191 // Add header first (use evaluate to avoid pointer-events issues in Firefox)
158192 await page . evaluate ( ( ) => ( window as any ) . addHeader ( ) ) ;
159193 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - t o p / , { timeout : 1000 } ) ;
194+ // Left/right should remain throughout
195+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
196+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
160197
161198 // Remove header
162199 await page . evaluate ( ( ) => ( window as any ) . removeHeader ( ) ) ;
163200
164- // Should have safe-area-top again
201+ // Should have safe-area-top again, left/right should remain
165202 await expect ( content ) . toHaveClass ( / s a f e - a r e a - t o p / , { timeout : 1000 } ) ;
203+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
204+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
166205 } ) ;
167206
168207 test ( 'content inside ion-tabs with tab bar should not have safe-area-bottom' , async ( { page } , testInfo ) => {
@@ -174,6 +213,9 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
174213 const content = page . locator ( '#content-dynamic-tabs' ) ;
175214 // Tab bar is present, so content should not have safe-area-bottom
176215 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
216+ // But left/right should still apply (main content)
217+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
218+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
177219 } ) ;
178220
179221 test ( 'dynamic tab bar removal should update safe-area classes' , async ( { page } , testInfo ) => {
@@ -186,12 +228,17 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
186228
187229 // Initially tab bar is present, so no safe-area-bottom
188230 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / ) ;
231+ // Left/right should be present throughout
232+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
233+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
189234
190235 // Remove tab bar
191236 await page . evaluate ( ( ) => ( window as any ) . removeTabBar ( ) ) ;
192237
193- // Should have safe-area-bottom now
238+ // Should have safe-area-bottom now, left/right remain
194239 await expect ( content ) . toHaveClass ( / s a f e - a r e a - b o t t o m / , { timeout : 1000 } ) ;
240+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
241+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
195242 } ) ;
196243
197244 test ( 'dynamic tab bar addition should update safe-area classes' , async ( { page } , testInfo ) => {
@@ -205,12 +252,17 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
205252 // Remove tab bar first
206253 await page . evaluate ( ( ) => ( window as any ) . removeTabBar ( ) ) ;
207254 await expect ( content ) . toHaveClass ( / s a f e - a r e a - b o t t o m / , { timeout : 1000 } ) ;
255+ // Left/right should be present throughout
256+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
257+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
208258
209259 // Add tab bar back
210260 await page . evaluate ( ( ) => ( window as any ) . addTabBar ( ) ) ;
211261
212- // Should not have safe-area-bottom anymore
262+ // Should not have safe-area-bottom anymore, left/right remain
213263 await expect ( content ) . not . toHaveClass ( / s a f e - a r e a - b o t t o m / , { timeout : 1000 } ) ;
264+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - l e f t / ) ;
265+ await expect ( content ) . toHaveClass ( / s a f e - a r e a - r i g h t / ) ;
214266 } ) ;
215267 } ) ;
216268} ) ;
0 commit comments