@ossy/app 3.10.0 → 3.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +11 -11
- package/runtime/document-meta.js +38 -1
- package/runtime/page-runtime.js +2 -0
- package/src/shell/LanguageSelect.jsx +11 -0
- package/src/shell/index.js +1 -0
- package/src/shell/mobileShellFocus.js +11 -3
- package/src/shell/useShellWorkspace.js +3 -0
- package/src/shell-registry/compact-mobile-shell.flow.js +114 -0
- package/src/shell-registry/head-default.component.jsx +2 -0
- package/src/shell-registry/switch-language.action.js +7 -0
- package/src/shell-registry/switch-language.flow.js +33 -0
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ export const metadata = {
|
|
|
60
60
|
}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
SSR emits `<meta name="description">`, `og:title`, `og:description`, `og:type=website`, and when `config.siteUrl` or `config.domain` is set, `og:url`. Root-relative `ogImage` values (e.g. `/og.png`) are absolutized against that origin. Prefer `{pageId}.metaDescription` in translation catalogs when the copy should be localized.
|
|
63
|
+
SSR emits `<meta name="theme-color">`, `<link rel="icon">` (default `/favicon.ico`, or `config.faviconHref`; set `faviconHref: null` to omit), `<meta name="description">`, `og:title`, `og:description`, `og:type=website`, and when `config.siteUrl` or `config.domain` is set, `og:url`. Root-relative `ogImage` values (e.g. `/og.png`) are absolutized against that origin. Prefer `{pageId}.metaDescription` in translation catalogs when the copy should be localized.
|
|
64
64
|
|
|
65
65
|
**Props:** The full app config is passed as props to the page component — `url`, `theme`, `isAuthenticated`, `pages`, `workspaceId`, `apiUrl`, etc. You can also access config via `useApp()` / `useRouter()` hooks from `@ossy/connected-components` and `@ossy/router-react`.
|
|
66
66
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -49,21 +49,21 @@
|
|
|
49
49
|
"@babel/eslint-parser": "^7.15.8",
|
|
50
50
|
"@babel/preset-react": "^7.26.3",
|
|
51
51
|
"@babel/register": "^7.25.9",
|
|
52
|
-
"@ossy/authentication": "^3.
|
|
53
|
-
"@ossy/design-system": "^3.
|
|
52
|
+
"@ossy/authentication": "^3.11.1",
|
|
53
|
+
"@ossy/design-system": "^3.11.0",
|
|
54
54
|
"@ossy/locale": "^3.4.0",
|
|
55
55
|
"@ossy/manifest": "^3.9.0",
|
|
56
|
-
"@ossy/package-catalog": "^3.
|
|
56
|
+
"@ossy/package-catalog": "^3.11.0",
|
|
57
57
|
"@ossy/pages": "^3.0.9",
|
|
58
|
-
"@ossy/platform": "^3.
|
|
59
|
-
"@ossy/resources": "^3.
|
|
58
|
+
"@ossy/platform": "^3.11.1",
|
|
59
|
+
"@ossy/resources": "^3.11.1",
|
|
60
60
|
"@ossy/router": "^3.0.9",
|
|
61
|
-
"@ossy/router-react": "^3.
|
|
61
|
+
"@ossy/router-react": "^3.11.0",
|
|
62
62
|
"@ossy/schema": "^3.8.0",
|
|
63
|
-
"@ossy/sdk": "^3.
|
|
64
|
-
"@ossy/sdk-react": "^3.
|
|
63
|
+
"@ossy/sdk": "^3.11.1",
|
|
64
|
+
"@ossy/sdk-react": "^3.11.0",
|
|
65
65
|
"@ossy/themes": "^3.8.0",
|
|
66
|
-
"@ossy/workspaces": "^3.
|
|
66
|
+
"@ossy/workspaces": "^3.11.1",
|
|
67
67
|
"@rollup/plugin-alias": "^6.0.0",
|
|
68
68
|
"@rollup/plugin-babel": "^7.1.0",
|
|
69
69
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
"README.md",
|
|
98
98
|
"tsconfig.json"
|
|
99
99
|
],
|
|
100
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "f279de3535b6bc76631cbc6b943dae8f92a1565f"
|
|
101
101
|
}
|
package/runtime/document-meta.js
CHANGED
|
@@ -138,7 +138,37 @@ export function resolveOpenGraphImage (metadata = {}, props = {}) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
|
-
*
|
|
141
|
+
* Resolve favicon URL from app settings.
|
|
142
|
+
* Default `/favicon.ico`. Use `null` or `false` to omit the link tag.
|
|
143
|
+
*
|
|
144
|
+
* @param {{ faviconHref?: string | null | false }} props
|
|
145
|
+
* @returns {string | null}
|
|
146
|
+
*/
|
|
147
|
+
export function resolveFaviconHref (props = {}) {
|
|
148
|
+
if (props.faviconHref === null || props.faviconHref === false) return null
|
|
149
|
+
if (typeof props.faviconHref === 'string') {
|
|
150
|
+
const trimmed = props.faviconHref.trim()
|
|
151
|
+
return trimmed || null
|
|
152
|
+
}
|
|
153
|
+
return '/favicon.ico'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve `<meta name="theme-color">` content from app settings.
|
|
158
|
+
*
|
|
159
|
+
* @param {{ themeColor?: string }} props
|
|
160
|
+
* @returns {string}
|
|
161
|
+
*/
|
|
162
|
+
export function resolveThemeColor (props = {}) {
|
|
163
|
+
if (typeof props.themeColor === 'string' && props.themeColor.trim()) {
|
|
164
|
+
return props.themeColor.trim()
|
|
165
|
+
}
|
|
166
|
+
return '#000000'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Build `<head>` children for SSR: charset, viewport, title, description, Open Graph,
|
|
171
|
+
* theme-color, and favicon.
|
|
142
172
|
*
|
|
143
173
|
* @param {{ metadata: object, props: object, viewportContent: string }} options
|
|
144
174
|
* @returns {import('react').ReactNode[]}
|
|
@@ -148,14 +178,21 @@ export function buildDocumentHeadChildren ({ metadata, props, viewportContent })
|
|
|
148
178
|
const description = resolveDocumentDescription(metadata, props)
|
|
149
179
|
const ogUrl = resolveOpenGraphUrl(props)
|
|
150
180
|
const ogImage = resolveOpenGraphImage(metadata, props)
|
|
181
|
+
const themeColor = resolveThemeColor(props)
|
|
182
|
+
const faviconHref = resolveFaviconHref(props)
|
|
151
183
|
|
|
152
184
|
/** @type {import('react').ReactNode[]} */
|
|
153
185
|
const children = [
|
|
154
186
|
createElement('meta', { key: 'charset', charSet: 'utf-8' }),
|
|
155
187
|
createElement('meta', { key: 'viewport', name: 'viewport', content: viewportContent }),
|
|
188
|
+
createElement('meta', { key: 'theme-color', name: 'theme-color', content: themeColor }),
|
|
156
189
|
createElement('title', { key: 'title' }, title),
|
|
157
190
|
]
|
|
158
191
|
|
|
192
|
+
if (faviconHref) {
|
|
193
|
+
children.push(createElement('link', { key: 'favicon', rel: 'icon', href: faviconHref }))
|
|
194
|
+
}
|
|
195
|
+
|
|
159
196
|
if (description) {
|
|
160
197
|
children.push(createElement('meta', { key: 'description', name: 'description', content: description }))
|
|
161
198
|
}
|
package/runtime/page-runtime.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { useRouter } from '@ossy/router-react'
|
|
10
10
|
import { languageCode } from './languageCode.js'
|
|
11
11
|
import { compactShellControlStyle } from './mobileShellFocus.js'
|
|
12
|
+
import { metadata as SwitchLanguage } from '../shell-registry/switch-language.action.js'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Language switcher for app shell header — cycle when two locales, dropdown when more.
|
|
@@ -32,10 +33,14 @@ export function LanguageSelect ({ compact = false }) {
|
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
35
|
<Button
|
|
36
|
+
{...SwitchLanguage}
|
|
35
37
|
variant="link"
|
|
36
38
|
prefix="select"
|
|
37
39
|
href={getHref({ language: other })}
|
|
40
|
+
label={undefined}
|
|
38
41
|
aria-label={pickerLabel}
|
|
42
|
+
data-language={other}
|
|
43
|
+
data-ossy-language={language}
|
|
39
44
|
style={controlStyle}
|
|
40
45
|
>
|
|
41
46
|
{languageCode(language)}
|
|
@@ -47,9 +52,12 @@ export function LanguageSelect ({ compact = false }) {
|
|
|
47
52
|
<Dropdown
|
|
48
53
|
trigger={(
|
|
49
54
|
<Button
|
|
55
|
+
{...SwitchLanguage}
|
|
50
56
|
prefix="select"
|
|
51
57
|
variant="link"
|
|
58
|
+
label={undefined}
|
|
52
59
|
aria-label={pickerLabel}
|
|
60
|
+
data-ossy-language={language}
|
|
53
61
|
style={controlStyle}
|
|
54
62
|
>
|
|
55
63
|
{languageCode(language)}
|
|
@@ -63,7 +71,10 @@ export function LanguageSelect ({ compact = false }) {
|
|
|
63
71
|
return (
|
|
64
72
|
<ContextMenu.Item
|
|
65
73
|
key={lang}
|
|
74
|
+
{...SwitchLanguage}
|
|
66
75
|
href={isActive ? undefined : getHref({ language: lang })}
|
|
76
|
+
label={undefined}
|
|
77
|
+
data-language={lang}
|
|
67
78
|
aria-current={isActive ? 'true' : undefined}
|
|
68
79
|
>
|
|
69
80
|
{languageCode(lang)}
|
package/src/shell/index.js
CHANGED
|
@@ -35,6 +35,7 @@ export {
|
|
|
35
35
|
} from './mobileShellFocus.js'
|
|
36
36
|
export { metadata as OpenMobileShellNav } from '../shell-registry/open-mobile-shell-nav.action.js'
|
|
37
37
|
export { metadata as CloseMobileShellNav } from '../shell-registry/close-mobile-shell-nav.action.js'
|
|
38
|
+
export { metadata as SwitchLanguage } from '../shell-registry/switch-language.action.js'
|
|
38
39
|
export { buildSidebarNav } from './buildSidebarNav.js'
|
|
39
40
|
export { resolvePackageHomePageId } from './resolvePackageHomePageId.js'
|
|
40
41
|
export { shellSlotUnset, shellSlotViewId, coerceShellSlotSpec } from '../../runtime/merge-shell-slots.js'
|
|
@@ -144,18 +144,26 @@ export const TAB_TRAP_FALLBACK = Object.freeze({ fallback: true })
|
|
|
144
144
|
* List Tab-reachable elements inside a drawer root.
|
|
145
145
|
* Skips `aria-hidden` nodes and elements that are not visible.
|
|
146
146
|
*
|
|
147
|
+
* Do **not** pass `checkOpacity: true` to `checkVisibility`. Overlay fade-in
|
|
148
|
+
* animates ancestor opacity 0→1; during that window Chromium reports every
|
|
149
|
+
* control as not visible, so the Tab trap sees an empty list and falls back
|
|
150
|
+
* to re-focusing the close control (breaking Shift+Tab wrap in e2e).
|
|
151
|
+
*
|
|
147
152
|
* @param {{ querySelectorAll?: Function } | null | undefined} root
|
|
148
153
|
* @returns {Element[]}
|
|
149
154
|
*/
|
|
150
155
|
export function listFocusable (root) {
|
|
151
156
|
if (!root?.querySelectorAll) return []
|
|
157
|
+
const active = typeof document !== 'undefined' ? document.activeElement : null
|
|
152
158
|
return [...root.querySelectorAll(FOCUSABLE_SELECTOR)].filter((el) => {
|
|
153
159
|
if (el.getAttribute?.('aria-hidden') === 'true') return false
|
|
160
|
+
// Keep the focused control in the trap even if visibility APIs disagree
|
|
161
|
+
// (e.g. mid fade-in or inert/aria-hidden ancestors outside the drawer).
|
|
162
|
+
if (el === active) return true
|
|
154
163
|
if (typeof el.checkVisibility === 'function') {
|
|
155
|
-
return el.checkVisibility({
|
|
164
|
+
return el.checkVisibility({ checkVisibilityCSS: true })
|
|
156
165
|
}
|
|
157
|
-
|
|
158
|
-
return el.offsetParent !== null || el === active
|
|
166
|
+
return el.offsetParent !== null
|
|
159
167
|
})
|
|
160
168
|
}
|
|
161
169
|
|
|
@@ -7,6 +7,9 @@ import { useApp } from './AppContext.js'
|
|
|
7
7
|
* Workspace list + active workspace for shell chrome.
|
|
8
8
|
* SSR and the first client paint use the signed user-app-settings cookie
|
|
9
9
|
* (merged into bootstrap props); live SDK reads refresh in the background.
|
|
10
|
+
*
|
|
11
|
+
* Shares the GetWorkspace / ListWorkspaces read-cache with feature pages —
|
|
12
|
+
* `invalidate(cacheKey(GetWorkspace))` / `refetch()` updates this hook.
|
|
10
13
|
*/
|
|
11
14
|
export function useShellWorkspace () {
|
|
12
15
|
const app = useApp()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { metadata as OpenMobileShellNav } from './open-mobile-shell-nav.action.js'
|
|
2
|
+
import { metadata as CloseMobileShellNav } from './close-mobile-shell-nav.action.js'
|
|
3
|
+
|
|
4
|
+
export const metadata = {
|
|
5
|
+
id: '@ossy/app/flows/compact-mobile-shell',
|
|
6
|
+
feature: 'app-shell',
|
|
7
|
+
requires: ['server'],
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
title: 'Compact mobile shell navbar',
|
|
12
|
+
description:
|
|
13
|
+
'At ≤900px the default layout hides the in-grid sidebar and opens primary nav from a header menu into a drawer (phone + iPad portrait); wider than 900px keeps desktop chrome (iPad landscape)',
|
|
14
|
+
steps: [
|
|
15
|
+
{ page: '@home' },
|
|
16
|
+
{ viewport: { width: 390, height: 844 } },
|
|
17
|
+
{ result: { action: OpenMobileShellNav, timeout: 15000 } },
|
|
18
|
+
{ action: OpenMobileShellNav },
|
|
19
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', timeout: 10000 } },
|
|
20
|
+
{ result: { selector: '[data-ossy-sidebar-presentation="drawer"]', timeout: 5000 } },
|
|
21
|
+
// Open focuses the close control (SHELL-SPEC §4.5)
|
|
22
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav-close]:focus', timeout: 5000 } },
|
|
23
|
+
// Background inert + body scroll lock while open
|
|
24
|
+
{ result: { selector: '[data-ossy-app-shell][aria-hidden="true"]', timeout: 5000 } },
|
|
25
|
+
{ result: { selector: 'body[data-ossy-mobile-shell-scroll-lock]', timeout: 5000 } },
|
|
26
|
+
// Tab cycles inside the drawer (wrap: Shift+Tab from close → last nav; Tab → close)
|
|
27
|
+
{ press: 'Shift+Tab' },
|
|
28
|
+
{
|
|
29
|
+
result: {
|
|
30
|
+
selector: '[data-ossy-mobile-shell-nav] [data-ossy-sidebar-nav-item]:focus',
|
|
31
|
+
timeout: 5000,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{ press: 'Tab' },
|
|
35
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav-close]:focus', timeout: 5000 } },
|
|
36
|
+
// Escape closes the drawer (SHELL-SPEC §4.5)
|
|
37
|
+
{ press: 'Escape' },
|
|
38
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', hidden: true, timeout: 5000 } },
|
|
39
|
+
{ result: { selector: '[data-ossy-app-shell][aria-hidden="true"]', hidden: true, timeout: 5000 } },
|
|
40
|
+
{ result: { selector: 'body[data-ossy-mobile-shell-scroll-lock]', hidden: true, timeout: 5000 } },
|
|
41
|
+
// Close restores focus to the menu trigger
|
|
42
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav-trigger]:focus', timeout: 5000 } },
|
|
43
|
+
{ result: { action: OpenMobileShellNav, timeout: 10000 } },
|
|
44
|
+
{ action: OpenMobileShellNav },
|
|
45
|
+
{ result: { action: CloseMobileShellNav, timeout: 5000 } },
|
|
46
|
+
{ action: CloseMobileShellNav },
|
|
47
|
+
{ result: { action: OpenMobileShellNav, timeout: 10000 } },
|
|
48
|
+
// Overlay backdrop click closes the drawer (click right of the left panel)
|
|
49
|
+
{ action: OpenMobileShellNav },
|
|
50
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav-overlay]', timeout: 10000 } },
|
|
51
|
+
{
|
|
52
|
+
click: {
|
|
53
|
+
selector: '[data-ossy-mobile-shell-nav-overlay]',
|
|
54
|
+
position: { x: 360, y: 200 },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', hidden: true, timeout: 5000 } },
|
|
58
|
+
{ result: { action: OpenMobileShellNav, timeout: 10000 } },
|
|
59
|
+
// Route change closes the drawer (SHELL-SPEC §4.5) — click a non-current sidebar link
|
|
60
|
+
{ action: OpenMobileShellNav },
|
|
61
|
+
{
|
|
62
|
+
result: {
|
|
63
|
+
selector: '[data-ossy-mobile-shell-nav] [data-ossy-sidebar-nav-item]:not([aria-current="page"])',
|
|
64
|
+
timeout: 10000,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
click: {
|
|
69
|
+
selector: '[data-ossy-mobile-shell-nav] [data-ossy-sidebar-nav-item]:not([aria-current="page"])',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', hidden: true, timeout: 10000 } },
|
|
73
|
+
{ result: { action: OpenMobileShellNav, timeout: 15000 } },
|
|
74
|
+
{ page: '@home' },
|
|
75
|
+
{ viewport: { width: 768, height: 1024 } },
|
|
76
|
+
{ result: { action: OpenMobileShellNav, timeout: 15000 } },
|
|
77
|
+
{ action: OpenMobileShellNav },
|
|
78
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', timeout: 10000 } },
|
|
79
|
+
{ result: { selector: '[data-ossy-sidebar-presentation="drawer"]', timeout: 5000 } },
|
|
80
|
+
{ result: { selector: '[data-ossy-app-shell][aria-hidden="true"]', timeout: 5000 } },
|
|
81
|
+
{ result: { selector: 'body[data-ossy-mobile-shell-scroll-lock]', timeout: 5000 } },
|
|
82
|
+
{ press: 'Escape' },
|
|
83
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', hidden: true, timeout: 5000 } },
|
|
84
|
+
{ result: { action: OpenMobileShellNav, timeout: 10000 } },
|
|
85
|
+
{ action: OpenMobileShellNav },
|
|
86
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav-overlay]', timeout: 10000 } },
|
|
87
|
+
{
|
|
88
|
+
click: {
|
|
89
|
+
selector: '[data-ossy-mobile-shell-nav-overlay]',
|
|
90
|
+
position: { x: 700, y: 200 },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', hidden: true, timeout: 5000 } },
|
|
94
|
+
{ result: { action: OpenMobileShellNav, timeout: 10000 } },
|
|
95
|
+
// iPad portrait: route change closes the drawer
|
|
96
|
+
{ action: OpenMobileShellNav },
|
|
97
|
+
{
|
|
98
|
+
result: {
|
|
99
|
+
selector: '[data-ossy-mobile-shell-nav] [data-ossy-sidebar-nav-item]:not([aria-current="page"])',
|
|
100
|
+
timeout: 10000,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
click: {
|
|
105
|
+
selector: '[data-ossy-mobile-shell-nav] [data-ossy-sidebar-nav-item]:not([aria-current="page"])',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{ result: { selector: '[data-ossy-mobile-shell-nav]', hidden: true, timeout: 10000 } },
|
|
109
|
+
{ result: { action: OpenMobileShellNav, timeout: 15000 } },
|
|
110
|
+
// iPad landscape (>900px): desktop chrome — no mobile menu control
|
|
111
|
+
{ viewport: { width: 1024, height: 768 } },
|
|
112
|
+
{ result: { action: OpenMobileShellNav, hidden: true, timeout: 10000 } },
|
|
113
|
+
],
|
|
114
|
+
}
|
|
@@ -2,6 +2,8 @@ import React from 'react'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Default document head assets for platform shell layouts (React 19 hoisting).
|
|
5
|
+
* Favicon and theme-color live in SSR `buildDocumentHeadChildren` so every layout
|
|
6
|
+
* (including blank) gets them; this slot keeps PWA companion links only.
|
|
5
7
|
*/
|
|
6
8
|
export function SiteDocumentHead () {
|
|
7
9
|
return (
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { metadata as SwitchLanguage } from './switch-language.action.js'
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
id: '@ossy/app/flows/switch-language',
|
|
5
|
+
feature: 'app-shell',
|
|
6
|
+
requires: ['server'],
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Happy path for issue #5 — header language control switches the active page
|
|
11
|
+
* locale and preserves the current query string (#726).
|
|
12
|
+
*/
|
|
13
|
+
export default {
|
|
14
|
+
title: 'Switch language',
|
|
15
|
+
description:
|
|
16
|
+
'Visitor opens packages with a query string, switches EN→SV via the header control, then back to EN while keeping the query',
|
|
17
|
+
steps: [
|
|
18
|
+
{ page: '@package-catalog/home', search: { q: 'e2e-lang' } },
|
|
19
|
+
{ result: { url: /\/packages\/?(\?|#|$)/, timeout: 15000 } },
|
|
20
|
+
{ result: { url: /[?&]q=e2e-lang/, timeout: 5000 } },
|
|
21
|
+
{ result: { selector: '[data-ossy-language="en"]', timeout: 10000 } },
|
|
22
|
+
{ result: { action: { ...SwitchLanguage, language: 'sv' }, timeout: 10000 } },
|
|
23
|
+
{ action: { ...SwitchLanguage, language: 'sv' } },
|
|
24
|
+
{ result: { selector: '[data-ossy-language="sv"]', timeout: 15000 } },
|
|
25
|
+
{ result: { url: /\/paket\/?(\?|#|$)/, timeout: 15000 } },
|
|
26
|
+
{ result: { url: /[?&]q=e2e-lang/, timeout: 5000 } },
|
|
27
|
+
{ result: { action: { ...SwitchLanguage, language: 'en' }, timeout: 10000 } },
|
|
28
|
+
{ action: { ...SwitchLanguage, language: 'en' } },
|
|
29
|
+
{ result: { selector: '[data-ossy-language="en"]', timeout: 15000 } },
|
|
30
|
+
{ result: { url: /\/packages\/?(\?|#|$)/, timeout: 15000 } },
|
|
31
|
+
{ result: { url: /[?&]q=e2e-lang/, timeout: 5000 } },
|
|
32
|
+
],
|
|
33
|
+
}
|