Skip to content

Commit d7961cc

Browse files
committed
test: adjust tests + also test non-DOM environment
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent e9beb1e commit d7961cc

11 files changed

Lines changed: 221 additions & 195 deletions

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@
4242
"lint": "eslint .",
4343
"lint:fix": "eslint --fix lib tests",
4444
"prerelease:format-changelog": "node build/format-changelog.mjs",
45-
"test": "LANG=en-US vitest run",
45+
"test": "npm run test:dom && npm run test:node",
4646
"test:coverage": "LANG=en-US vitest run --coverage",
47+
"test:dom": "LANG=en-US vitest run",
48+
"test:node": "LANG=en-US vitest run --environment node",
4749
"test:watch": "LANG=en-US vitest watch"
4850
},
4951
"browserslist": [

tests/date.test.ts

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as localeModule from '../lib/locale.ts'
1313
vi.mock('../lib/locale.ts')
1414
const { getCanonicalLocale } = localeModule as Mocked<typeof localeModule>
1515

16-
declare let window: Nextcloud.v24.WindowWithGlobals
16+
declare let globalThis: Nextcloud.v29.WindowWithGlobals
1717

1818
describe('date', () => {
1919
describe('getFirstDay', () => {
@@ -35,7 +35,7 @@ describe('date', () => {
3535

3636
beforeEach(() => {
3737
// @ts-expect-error - Mocking for tests
38-
delete window.firstDay
38+
delete globalThis.firstDay
3939
})
4040

4141
afterEach(() => {
@@ -44,38 +44,38 @@ describe('date', () => {
4444
Object.defineProperty(Intl.Locale.prototype, 'weekInfo', originalWeekInfoDescriptor)
4545
})
4646

47-
it('returns `window.firstDate` when defined', () => {
48-
window.firstDay = 3
47+
it('returns `globalThis.firstDate` when defined', () => {
48+
globalThis.firstDay = 3
4949
expect(getFirstDay()).toBe(3)
5050
})
5151

52-
it('returns 1 (Monday) in "de-DE" locale when `window.firstDate` is not defined but `Intl.weekInfo` is defined (Node.js, Samsung Internet)', () => {
52+
it('returns 1 (Monday) in "de-DE" locale when `globalThis.firstDate` is not defined but `Intl.weekInfo` is defined (Node.js, Samsung Internet)', () => {
5353
getCanonicalLocale.mockReturnValue('de-DE')
5454
expect(getFirstDay()).toBe(1)
5555
})
5656

57-
it('returns 0 (Sunday) in "en-US" locale when `window.firstDate` is not defined but `Intl.weekInfo` is defined (Node.js, Samsung Internet)', () => {
57+
it('returns 0 (Sunday) in "en-US" locale when `globalThis.firstDate` is not defined but `Intl.weekInfo` is defined (Node.js, Samsung Internet)', () => {
5858
getCanonicalLocale.mockReturnValue('en-US')
5959
expect(getFirstDay()).toBe(0)
6060
})
6161

62-
it('returns 1 (Monday) in "de-DE" locale when `window.firstDate` is not defined but `Intl.getWeekInfo` is defined (Chromium, Safari)', () => {
62+
it('returns 1 (Monday) in "de-DE" locale when `globalThis.firstDate` is not defined but `Intl.getWeekInfo` is defined (Chromium, Safari)', () => {
6363
getCanonicalLocale.mockReturnValue('de-DE')
6464
// getWeekInfo is available, weekInfo is not available (Chromium, Safari)
6565
Intl.Locale.prototype.getWeekInfo = mockGetWeekInfo
6666
delete Intl.Locale.prototype.weekInfo
6767
expect(getFirstDay()).toBe(1)
6868
})
6969

70-
it('returns 0 (Sunday) in "en-US" locale when `window.firstDate` is not defined but `Intl.getWeekInfo` is defined (Chromium, Safari)', () => {
70+
it('returns 0 (Sunday) in "en-US" locale when `globalThis.firstDate` is not defined but `Intl.getWeekInfo` is defined (Chromium, Safari)', () => {
7171
getCanonicalLocale.mockReturnValue('en-US')
7272
// getWeekInfo is available, weekInfo is not available (Chromium, Safari)
7373
Intl.Locale.prototype.getWeekInfo = mockGetWeekInfo
7474
delete Intl.Locale.prototype.weekInfo
7575
expect(getFirstDay()).toBe(0)
7676
})
7777

78-
it('returns 1 (Monday) when neither `window.firstDate` nor `Intl.Locale.getWeekInfo`/`weekInfo` is defined (Firefox)', () => {
78+
it('returns 1 (Monday) when neither `globalThis.firstDate` nor `Intl.Locale.getWeekInfo`/`weekInfo` is defined (Firefox)', () => {
7979
delete Intl.Locale.prototype.getWeekInfo
8080
delete Intl.Locale.prototype.weekInfo
8181
expect(getFirstDay()).toBe(1)
@@ -85,20 +85,20 @@ describe('date', () => {
8585
describe('getDayNames', () => {
8686
afterEach(() => {
8787
// @ts-expect-error - Mocking for tests
88-
delete window.dayNames
88+
delete globalThis.dayNames
8989
})
9090

91-
it('returns `window.dayNames` when defined', () => {
92-
window.dayNames = ['Day 0', 'Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6']
93-
expect(getDayNames()).toEqual(window.dayNames)
91+
it('returns `globalThis.dayNames` when defined', () => {
92+
globalThis.dayNames = ['Day 0', 'Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6']
93+
expect(getDayNames()).toEqual(globalThis.dayNames)
9494
})
9595

96-
it('returns English day names in "en-US" locale when `window.dayNames` is not defined', () => {
96+
it('returns English day names in "en-US" locale when `globalThis.dayNames` is not defined', () => {
9797
getCanonicalLocale.mockReturnValue('en-US')
9898
expect(getDayNames()).toEqual(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'])
9999
})
100100

101-
it('returns German day names in "de-DE" locale when `window.dayNames` is not defined', () => {
101+
it('returns German day names in "de-DE" locale when `globalThis.dayNames` is not defined', () => {
102102
getCanonicalLocale.mockReturnValue('de-DE')
103103
expect(getDayNames()).toEqual(['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'])
104104
})
@@ -107,20 +107,20 @@ describe('date', () => {
107107
describe('getDayNamesShort', () => {
108108
afterEach(() => {
109109
// @ts-expect-error - Mocking for tests
110-
delete window.dayNamesShort
110+
delete globalThis.dayNamesShort
111111
})
112112

113-
it('returns `window.dayNamesShort` when defined', () => {
114-
window.dayNamesShort = ['D. 0', 'D. 1', 'D. 2', 'D. 3', 'D. 4', 'D. 5', 'D. 6']
115-
expect(getDayNamesShort()).toEqual(window.dayNamesShort)
113+
it('returns `globalThis.dayNamesShort` when defined', () => {
114+
globalThis.dayNamesShort = ['D. 0', 'D. 1', 'D. 2', 'D. 3', 'D. 4', 'D. 5', 'D. 6']
115+
expect(getDayNamesShort()).toEqual(globalThis.dayNamesShort)
116116
})
117117

118-
it('returns English short day names from `Intl` in "en-US" locale when `window.dayNamesShort` is not defined', () => {
118+
it('returns English short day names from `Intl` in "en-US" locale when `globalThis.dayNamesShort` is not defined', () => {
119119
getCanonicalLocale.mockReturnValue('en-US')
120120
expect(getDayNamesShort()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])
121121
})
122122

123-
it('returns German short day names from `Intl` in "de-DE" locale when `window.dayNamesShort` is not defined', () => {
123+
it('returns German short day names from `Intl` in "de-DE" locale when `globalThis.dayNamesShort` is not defined', () => {
124124
getCanonicalLocale.mockReturnValue('de-DE')
125125
expect(getDayNamesShort()).toEqual(['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'])
126126
})
@@ -129,20 +129,20 @@ describe('date', () => {
129129
describe('getDayNamesMin', () => {
130130
afterEach(() => {
131131
// @ts-expect-error - Mocking for tests
132-
delete window.dayNamesMin
132+
delete globalThis.dayNamesMin
133133
})
134134

135-
it('returns `window.dayNamesMin` when defined', () => {
136-
window.dayNamesMin = ['D0', 'D1', 'D2', 'D3', 'D4', 'D5', 'D6']
137-
expect(getDayNamesMin()).toEqual(window.dayNamesMin)
135+
it('returns `globalThis.dayNamesMin` when defined', () => {
136+
globalThis.dayNamesMin = ['D0', 'D1', 'D2', 'D3', 'D4', 'D5', 'D6']
137+
expect(getDayNamesMin()).toEqual(globalThis.dayNamesMin)
138138
})
139139

140-
it('returns English narrow day names from `Intl` in "en-US" locale when `window.dayNamesMin` is not defined', () => {
140+
it('returns English narrow day names from `Intl` in "en-US" locale when `globalThis.dayNamesMin` is not defined', () => {
141141
getCanonicalLocale.mockReturnValue('en-US')
142142
expect(getDayNamesMin()).toEqual(['S', 'M', 'T', 'W', 'T', 'F', 'S'])
143143
})
144144

145-
it('returns German narrow day names from `Intl` in "de-DE" locale when `window.dayNamesMin` is not defined', () => {
145+
it('returns German narrow day names from `Intl` in "de-DE" locale when `globalThis.dayNamesMin` is not defined', () => {
146146
getCanonicalLocale.mockReturnValue('de-DE')
147147
expect(getDayNamesMin()).toEqual(['S', 'M', 'D', 'M', 'D', 'F', 'S'])
148148
})
@@ -151,20 +151,20 @@ describe('date', () => {
151151
describe('getMonthNames', () => {
152152
afterEach(() => {
153153
// @ts-expect-error - Mocking for tests
154-
delete window.monthNames
154+
delete globalThis.monthNames
155155
})
156156

157-
it('returns `window.monthNames` when defined', () => {
158-
window.monthNames = ['Month 0', 'Month 1', 'Month 2', 'Month 3', 'Month 4', 'Month 5', 'Month 6', 'Month 7', 'Month 8', 'Month 9', 'Month 10', 'Month 11']
159-
expect(getMonthNames()).toEqual(window.monthNames)
157+
it('returns `globalThis.monthNames` when defined', () => {
158+
globalThis.monthNames = ['Month 0', 'Month 1', 'Month 2', 'Month 3', 'Month 4', 'Month 5', 'Month 6', 'Month 7', 'Month 8', 'Month 9', 'Month 10', 'Month 11']
159+
expect(getMonthNames()).toEqual(globalThis.monthNames)
160160
})
161161

162-
it('returns English month names from `Intl` in "en-US" locale when `window.monthNames` is not defined', () => {
162+
it('returns English month names from `Intl` in "en-US" locale when `globalThis.monthNames` is not defined', () => {
163163
getCanonicalLocale.mockReturnValue('en-US')
164164
expect(getMonthNames()).toEqual(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'])
165165
})
166166

167-
it('returns German month names from `Intl` in "de-DE" locale when `window.monthNames` is not defined', () => {
167+
it('returns German month names from `Intl` in "de-DE" locale when `globalThis.monthNames` is not defined', () => {
168168
getCanonicalLocale.mockReturnValue('de-DE')
169169
expect(getMonthNames()).toEqual(['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'])
170170
})
@@ -173,20 +173,20 @@ describe('date', () => {
173173
describe('getMonthNamesShort', () => {
174174
afterEach(() => {
175175
// @ts-expect-error - Mocking for tests
176-
delete window.monthNamesShort
176+
delete globalThis.monthNamesShort
177177
})
178178

179-
it('returns `window.monthNamesShort` when defined', () => {
180-
window.monthNamesShort = ['M. 0', 'M. 1', 'M. 2', 'M. 3', 'M. 4', 'M. 5', 'M. 6', 'M. 7', 'M. 8', 'M. 9', 'M. 10', 'M. 11']
181-
expect(getMonthNamesShort()).toEqual(window.monthNamesShort)
179+
it('returns `globalThis.monthNamesShort` when defined', () => {
180+
globalThis.monthNamesShort = ['M. 0', 'M. 1', 'M. 2', 'M. 3', 'M. 4', 'M. 5', 'M. 6', 'M. 7', 'M. 8', 'M. 9', 'M. 10', 'M. 11']
181+
expect(getMonthNamesShort()).toEqual(globalThis.monthNamesShort)
182182
})
183183

184-
it('returns English short month names from `Intl` in "en-US" locale when `window.monthNamesShort` is not defined', () => {
184+
it('returns English short month names from `Intl` in "en-US" locale when `globalThis.monthNamesShort` is not defined', () => {
185185
getCanonicalLocale.mockReturnValue('en-US')
186186
expect(getMonthNamesShort()).toEqual(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
187187
})
188188

189-
it('returns German short month names from `Intl` in "de-DE" locale when `window.monthNamesShort` is not defined', () => {
189+
it('returns German short month names from `Intl` in "de-DE" locale when `globalThis.monthNamesShort` is not defined', () => {
190190
getCanonicalLocale.mockReturnValue('de-DE')
191191
expect(getMonthNamesShort()).toEqual(['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'])
192192
})

tests/gettext.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { po } from 'gettext-parser'
77
import { beforeEach, describe, expect, it, vi } from 'vitest'
88
import { getGettextBuilder } from '../lib/gettext.ts'
99

10-
const setLanguage = (lang: string) => document.documentElement.setAttribute('lang', lang)
10+
const setLanguage = (lang: string) => { globalThis._nc_l10n_language = lang }
1111

1212
describe('gettext', () => {
1313
beforeEach(() => {

tests/is-rtl.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
6+
import { beforeEach, describe, expect, it } from 'vitest'
7+
import { isRTL } from '../lib/locale'
8+
9+
const setLanguage = (lang: string) => { globalThis._nc_l10n_language = lang }
10+
11+
describe('isRTL', () => {
12+
beforeEach(() => { globalThis._nc_l10n_locale = 'en' })
13+
14+
it('falls back to English which is LTR', () => {
15+
// Expect fallback which is English = LTR
16+
expect(isRTL()).toBe(false)
17+
})
18+
19+
it('uses the given argument over the current language', () => {
20+
// If a value is given it should use that language over the fallback
21+
expect(isRTL('ar')).toBe(true)
22+
setLanguage('ar')
23+
expect(isRTL('de')).toBe(false)
24+
})
25+
26+
it('without an argument the current language is used', () => {
27+
// It uses the configured language
28+
setLanguage('he')
29+
expect(isRTL()).toBe(true)
30+
})
31+
32+
it('without an argument the current language is used', () => {
33+
// It uses the configured language
34+
setLanguage('he')
35+
expect(isRTL()).toBe(true)
36+
})
37+
38+
it('handles Uzbek Afghan correctly', () => {
39+
// Given as argument
40+
expect(isRTL('uz')).toBe(false)
41+
expect(isRTL('uz-AF')).toBe(true)
42+
43+
// configured as current language
44+
setLanguage('uz')
45+
expect(isRTL()).toBe(false)
46+
47+
setLanguage('uz-AF')
48+
expect(isRTL()).toBe(true)
49+
})
50+
})

tests/loadTranslations.test.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,43 @@ import { setupServer } from 'msw/node'
1010
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
1111
import { loadTranslations, register, translate, unregister } from '../lib/translation.ts'
1212

13-
const setLanguage = (lang: string) => document.documentElement.setAttribute('lang', lang)
13+
vi.mock('@nextcloud/router', () => ({
14+
generateFilePath: (app: string, type: string, path: string) => `http://localhost/${app}/${type}/${path}`,
15+
}))
16+
17+
/**
18+
* Mock the langauge
19+
*
20+
* @param lang - The language to mock
21+
*/
22+
function setLanguage(lang: string): void {
23+
globalThis._nc_l10n_language = lang
24+
}
1425

1526
const requestHandlers = [
16-
http.get('/myapp/l10n/de.json', () => HttpResponse.json({
27+
http.get('http://localhost/myapp/l10n/de.json', () => HttpResponse.json({
1728
translations: {
1829
'Hello world!': 'Hallo Welt!',
1930
},
2031
})),
2132
// Response with empty body
22-
http.get('/empty/l10n/de.json', () => HttpResponse.json()),
33+
http.get('http://localhost/empty/l10n/de.json', () => HttpResponse.json()),
2334
// Response contains JSON but no translations
24-
http.get('/missing-bundle/l10n/de.json', () => HttpResponse.json({})),
35+
http.get('http://localhost/missing-bundle/l10n/de.json', () => HttpResponse.json({})),
2536
// Response contains JSON but the translations are invalid
26-
http.get('/invalid/l10n/de.json', () => HttpResponse.json({
37+
http.get('http://localhost/invalid/l10n/de.json', () => HttpResponse.json({
2738
translations: 'invalid',
2839
})),
2940
// errors
30-
http.get('/404/l10n/de.json', () => HttpResponse.json({}, { status: 404 })),
31-
http.get('/500/l10n/de.json', () => HttpResponse.json({}, { status: 500 })),
32-
http.get('/networkissue/l10n/de.json', () => HttpResponse.error()),
41+
http.get('http://localhost/404/l10n/de.json', () => HttpResponse.json({}, { status: 404 })),
42+
http.get('http://localhost/500/l10n/de.json', () => HttpResponse.json({}, { status: 500 })),
43+
http.get('http://localhost/networkissue/l10n/de.json', () => HttpResponse.error()),
3344
]
3445

3546
const server: SetupServerApi = setupServer(...requestHandlers)
3647

3748
describe('loadTranslations', () => {
3849
beforeAll(() => {
39-
// Mock some server state
40-
41-
(window as any)._oc_webroot = '';
42-
43-
(window as any)._oc_appswebroots = {
44-
404: '/404',
45-
500: '/500',
46-
empty: '/empty',
47-
invalid: '/invalid',
48-
'missing-bundle': '/missing-bundle',
49-
myapp: '/myapp',
50-
networkissue: '/networkissue',
51-
}
52-
5350
server.listen({ onUnhandledRequest: 'error' })
5451
})
5552

@@ -160,6 +157,6 @@ describe('loadTranslations', () => {
160157
})
161158

162159
it('does reject on empty response', async () => {
163-
expect(() => loadTranslations('empty')).rejects.toThrowErrorMatchingInlineSnapshot('[Error: Invalid content of translation bundle]')
160+
await expect(() => loadTranslations('empty')).rejects.toThrowErrorMatchingInlineSnapshot('[Error: Invalid content of translation bundle]')
164161
})
165162
})

0 commit comments

Comments
 (0)