Skip to content

Commit cc6e894

Browse files
committed
feat: scope global state to major version of library
Problem ------- In the past when we did breaking changes on the API we could not ensure that all apps loaded are already migrated and so some of them were still using the old files integrations. Because the global scope was not versioned those apps registered e.g. invalid actions on the files app causing the files app to break. Solution -------- With the changes on this PR we versioning the global state of the files integrations. So we know that the files app will only load integrations that are using the same major version of the library so prevent breaking the whole app. Note: This is not a breaking change API wise as no exposed API has changed, but any app now needs to use at least this version of the library to properly integrate into the files app. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent b39e111 commit cc6e894

21 files changed

Lines changed: 257 additions & 199 deletions

__tests__/actions/fileAction.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import type { Folder, Node } from '../../lib/node/index.ts'
99

1010
import { beforeEach, describe, expect, test, vi } from 'vitest'
1111
import { DefaultType, getFileActions, registerFileAction } from '../../lib/actions/index.ts'
12+
import { scopedGlobals } from '../../lib/globalScope.ts'
1213
import { getRegistry } from '../../lib/registry.ts'
1314
import logger from '../../lib/utils/logger.ts'
1415

1516
const folder = {} as Folder
1617
const view = {} as View
1718
describe('FileActions init', () => {
1819
beforeEach(() => {
19-
delete window._nc_fileactions
20+
delete scopedGlobals.fileActions
2021
})
2122

2223
test('Getting empty uninitialized FileActions', () => {
@@ -39,7 +40,7 @@ describe('FileActions init', () => {
3940

4041
registerFileAction(action)
4142

42-
expect(window._nc_fileactions).toHaveLength(1)
43+
expect(scopedGlobals.fileActions).toHaveLength(1)
4344
expect(getFileActions()).toHaveLength(1)
4445
expect(getFileActions()[0]).toStrictEqual(action)
4546
})

__tests__/actions/fileListAction.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { Folder } from '../../lib/node/index.ts'
99

1010
import { beforeEach, describe, expect, test, vi } from 'vitest'
1111
import { getFileListActions, registerFileListAction } from '../../lib/actions/fileListAction.ts'
12+
import { scopedGlobals } from '../../lib/globalScope.ts'
1213
import { getRegistry } from '../../lib/registry.ts'
1314
import logger from '../../lib/utils/logger.ts'
1415

@@ -28,11 +29,11 @@ function mockAction(id: string): IFileListAction {
2829

2930
describe('FileListActions init', () => {
3031
beforeEach(() => {
31-
delete window._nc_filelistactions
32+
delete scopedGlobals.fileListActions
3233
})
3334

3435
test('Uninitialized file list actions', () => {
35-
expect(window._nc_filelistactions).toBe(undefined)
36+
expect(scopedGlobals.fileListActions).toBe(undefined)
3637
const actions = getFileListActions()
3738
expect(actions).toHaveLength(0)
3839
})

__tests__/dav/davProperties.spec.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,27 @@ import {
1515
getRecentSearch,
1616
registerDavProperty,
1717
} from '../../lib/dav/davProperties.ts'
18+
import { scopedGlobals } from '../../lib/globalScope.ts'
1819
import logger from '../../lib/utils/logger.ts'
1920

2021
describe('DAV Properties', () => {
2122
beforeEach(() => {
22-
delete window._nc_dav_properties
23-
delete window._nc_dav_namespaces
23+
delete scopedGlobals.davNamespaces
24+
delete scopedGlobals.davProperties
2425

2526
logger.error = vi.fn()
2627
logger.warn = vi.fn()
2728
})
2829

2930
test('getDavNameSpaces fall back to defaults', () => {
30-
expect(window._nc_dav_namespaces).toBeUndefined()
31+
expect(scopedGlobals.davNamespaces).toBeUndefined()
3132
const namespace = getDavNameSpaces()
3233
expect(namespace).toBeTruthy()
3334
Object.keys(defaultDavNamespaces).forEach((n) => expect(namespace.includes(n) && namespace.includes(defaultDavNamespaces[n])).toBe(true))
3435
})
3536

3637
test('getDavProperties fall back to defaults', () => {
37-
expect(window._nc_dav_properties).toBeUndefined()
38+
expect(scopedGlobals.davProperties).toBeUndefined()
3839
const props = getDavProperties()
3940
expect(props).toBeTruthy()
4041
defaultDavProperties.forEach((p) => expect(props.includes(p)).toBe(true))
@@ -56,8 +57,8 @@ describe('DAV Properties', () => {
5657
})
5758

5859
test('registerDavProperty registers successfully', () => {
59-
expect(window._nc_dav_namespaces).toBeUndefined()
60-
expect(window._nc_dav_properties).toBeUndefined()
60+
expect(scopedGlobals.davNamespaces).toBeUndefined()
61+
expect(scopedGlobals.davProperties).toBeUndefined()
6162

6263
expect(registerDavProperty('my:prop', { my: 'https://example.com/ns' })).toBe(true)
6364
expect(logger.warn).not.toBeCalled()
@@ -67,8 +68,8 @@ describe('DAV Properties', () => {
6768
})
6869

6970
test('registerDavProperty fails when registered multiple times', () => {
70-
expect(window._nc_dav_namespaces).toBeUndefined()
71-
expect(window._nc_dav_properties).toBeUndefined()
71+
expect(scopedGlobals.davNamespaces).toBeUndefined()
72+
expect(scopedGlobals.davProperties).toBeUndefined()
7273

7374
expect(registerDavProperty('my:prop', { my: 'https://example.com/ns' })).toBe(true)
7475
expect(registerDavProperty('my:prop')).toBe(false)
@@ -80,8 +81,8 @@ describe('DAV Properties', () => {
8081
})
8182

8283
test('registerDavProperty fails with invalid props', () => {
83-
expect(window._nc_dav_namespaces).toBeUndefined()
84-
expect(window._nc_dav_properties).toBeUndefined()
84+
expect(scopedGlobals.davNamespaces).toBeUndefined()
85+
expect(scopedGlobals.davProperties).toBeUndefined()
8586

8687
expect(registerDavProperty('my:prop:invalid', { my: 'https://example.com/ns' })).toBe(false)
8788
expect(logger.error).toBeCalled()
@@ -95,8 +96,8 @@ describe('DAV Properties', () => {
9596
})
9697

9798
test('registerDavProperty fails with missing namespace', () => {
98-
expect(window._nc_dav_namespaces).toBeUndefined()
99-
expect(window._nc_dav_properties).toBeUndefined()
99+
expect(scopedGlobals.davNamespaces).toBeUndefined()
100+
expect(scopedGlobals.davProperties).toBeUndefined()
100101

101102
expect(registerDavProperty('my:prop', { other: 'https://example.com/ns' })).toBe(false)
102103
expect(logger.error).toBeCalled()

__tests__/filters/listFilter.spec.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/*
22
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
@@ -7,6 +7,7 @@ import type { IFileListFilterChip } from '../../lib/filters/index.ts'
77

88
import { beforeEach, describe, expect, test, vi } from 'vitest'
99
import { FileListFilter, getFileListFilters, registerFileListFilter, unregisterFileListFilter } from '../../lib/filters/index.ts'
10+
import { scopedGlobals } from '../../lib/globalScope.ts'
1011
import { getRegistry } from '../../lib/registry.ts'
1112

1213
class TestFilter extends FileListFilter {
@@ -83,16 +84,16 @@ describe('File list filter class', () => {
8384

8485
describe('File list filter functions', () => {
8586
beforeEach(() => {
86-
delete window._nc_filelist_filters
87+
delete scopedGlobals.fileListFilters
8788
})
8889

8990
test('can register a filter', () => {
9091
const filter = new FileListFilter('my:id', 50)
9192

9293
registerFileListFilter(filter)
93-
expect(window._nc_filelist_filters).toBeTypeOf('object')
94-
expect(window._nc_filelist_filters!.has(filter.id)).toBe(true)
95-
expect(window._nc_filelist_filters!.get(filter.id)).toBe(filter)
94+
expect(scopedGlobals.fileListFilters).toBeTypeOf('object')
95+
expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(true)
96+
expect(scopedGlobals.fileListFilters!.get(filter.id)).toBe(filter)
9697
})
9798

9899
test('register a filter emits event', () => {
@@ -101,7 +102,7 @@ describe('File list filter functions', () => {
101102

102103
getRegistry().addEventListener('register:listFilter', spy)
103104

104-
expect(window._nc_filelist_filters).toBe(undefined)
105+
expect(scopedGlobals.fileListFilters).toBe(undefined)
105106

106107
registerFileListFilter(filter)
107108
expect(spy).toHaveBeenCalled()
@@ -121,22 +122,22 @@ describe('File list filter functions', () => {
121122
const filter = new FileListFilter('my:id')
122123

123124
registerFileListFilter(filter)
124-
expect(window._nc_filelist_filters!.has(filter.id)).toBe(true)
125+
expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(true)
125126

126127
// test
127128
unregisterFileListFilter(filter.id)
128-
expect(window._nc_filelist_filters!.has(filter.id)).toBe(false)
129+
expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(false)
129130
})
130131

131132
test('unregister a filter twice does not throw', () => {
132133
const filter = new FileListFilter('my:id')
133134

134135
registerFileListFilter(filter)
135-
expect(window._nc_filelist_filters!.has(filter.id)).toBe(true)
136+
expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(true)
136137

137138
// test
138139
unregisterFileListFilter(filter.id)
139-
expect(window._nc_filelist_filters!.has(filter.id)).toBe(false)
140+
expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(false)
140141
expect(() => unregisterFileListFilter(filter.id)).not.toThrow()
141142
})
142143

@@ -150,7 +151,7 @@ describe('File list filter functions', () => {
150151
unregisterFileListFilter(filter.id)
151152
expect(spy).toHaveBeenCalled()
152153
expect(spy.mock.calls[0][0]).toBeInstanceOf(CustomEvent)
153-
expect(spy.mock.calls[0][0].detail).toBe(filter)
154+
expect(spy.mock.calls[0][0].detail).toBe(filter.id)
154155
})
155156

156157
test('can get registered filters', () => {

__tests__/headers/listHeaders.spec.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1-
/**
1+
/*
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

66
import type { IFileListHeader, IFolder, IView } from '../../lib/index.ts'
77

88
import { beforeEach, describe, expect, test, vi } from 'vitest'
9+
import { scopedGlobals } from '../../lib/globalScope.ts'
910
import { getFileListHeaders, registerFileListHeader } from '../../lib/headers/index.ts'
1011
import { getRegistry } from '../../lib/registry.ts'
1112
import logger from '../../lib/utils/logger.ts'
1213

1314
describe('FileListHeader init', () => {
1415
beforeEach(() => {
15-
delete window._nc_filelistheader
16+
delete scopedGlobals.fileListHeaders
1617
})
1718

1819
test('Getting empty uninitialized FileListHeader', () => {
1920
const headers = getFileListHeaders()
20-
expect(window._nc_filelistheader).toBeDefined()
21+
expect(Array.isArray(headers)).toBe(true)
2122
expect(headers).toHaveLength(0)
2223
})
2324

@@ -29,14 +30,9 @@ describe('FileListHeader init', () => {
2930
render: () => {},
3031
updated: () => {},
3132
}
32-
33-
expect(header.id).toBe('test')
34-
expect(header.order).toBe(1)
35-
expect(header.enabled!({} as IFolder, {} as IView)).toBe(true)
36-
3733
registerFileListHeader(header)
3834

39-
expect(window._nc_filelistheader).toHaveLength(1)
35+
expect(scopedGlobals.fileListHeaders).toHaveLength(1)
4036
expect(getFileListHeaders()).toHaveLength(1)
4137
expect(getFileListHeaders()[0]).toStrictEqual(header)
4238
})
@@ -60,6 +56,25 @@ describe('FileListHeader init', () => {
6056
expect(callback.mock.calls[0][0].detail).toBe(header)
6157
})
6258

59+
test('getFileListHeaders() returns array', () => {
60+
expect(getFileListHeaders()).toHaveLength(0)
61+
62+
const header: IFileListHeader = {
63+
id: 'test',
64+
order: 1,
65+
enabled: () => true,
66+
render: () => {},
67+
updated: () => {},
68+
}
69+
70+
registerFileListHeader(header)
71+
72+
const headers = getFileListHeaders()
73+
expect(Array.isArray(headers)).toBe(true)
74+
expect(headers).toHaveLength(1)
75+
expect(headers[0]).toStrictEqual(header)
76+
})
77+
6378
test('Duplicate Header gets rejected', () => {
6479
logger.error = vi.fn()
6580
const header: IFileListHeader = {

__tests__/index.spec.ts

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { NewMenuEntry } from '../lib/newMenu/NewMenu.ts'
7-
86
import { describe, expect, test } from 'vitest'
97
import { getFileActions, registerFileAction } from '../lib/actions/fileAction.ts'
108
import {
@@ -18,7 +16,6 @@ import {
1816
Permission,
1917
removeNewFileMenuEntry,
2018
} from '../lib/index.ts'
21-
import { NewMenu } from '../lib/newMenu/NewMenu.ts'
2219

2320
describe('Exports checks', () => {
2421
test('formatFileSize', () => {
@@ -76,38 +73,3 @@ describe('Exports checks', () => {
7673
expect(typeof Node).toBe('function')
7774
})
7875
})
79-
80-
describe('NewFileMenu methods', () => {
81-
const entry = {
82-
id: 'empty-file',
83-
displayName: 'Create empty file',
84-
templateName: 'New file.txt',
85-
iconSvgInline: '<svg></svg>',
86-
handler: () => {},
87-
} as NewMenuEntry
88-
89-
test('Init NewFileMenu', () => {
90-
expect(window._nc_newfilemenu).toBeUndefined()
91-
92-
const menuEntries = getNewFileMenuEntries()
93-
expect(menuEntries).toHaveLength(0)
94-
95-
expect(window._nc_newfilemenu).toBeDefined()
96-
expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
97-
})
98-
99-
test('Use existing initialized NewMenu', () => {
100-
expect(window._nc_newfilemenu).toBeDefined()
101-
expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
102-
103-
addNewFileMenuEntry(entry)
104-
105-
expect(window._nc_newfilemenu).toBeDefined()
106-
expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
107-
108-
removeNewFileMenuEntry(entry)
109-
110-
expect(window._nc_newfilemenu).toBeDefined()
111-
expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
112-
})
113-
})

__tests__/navigation.spec.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
/**
1+
/*
22
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

66
import { describe, expect, it, vi } from 'vitest'
7+
import { scopedGlobals } from '../lib/globalScope.ts'
78
import { View } from '../lib/index.ts'
89
import { getNavigation, Navigation } from '../lib/navigation/navigation.ts'
910
import { mockView } from './fixtures/view.ts'
@@ -18,21 +19,15 @@ describe('getNavigation', () => {
1819
})
1920

2021
it('stores the navigation globally', () => {
21-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
22-
// @ts-ignore
23-
delete window._nc_navigation
22+
delete scopedGlobals.navigation
2423
const navigation = getNavigation()
2524
expect(navigation).toBeInstanceOf(Navigation)
26-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
27-
// @ts-ignore
28-
expect(window._nc_navigation).toBeInstanceOf(Navigation)
25+
expect(scopedGlobals.navigation).toBeInstanceOf(Navigation)
2926
})
3027

3128
it('reuses an existing navigation', () => {
3229
const navigation = new Navigation()
33-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
34-
// @ts-ignore
35-
window._nc_navigation = navigation
30+
scopedGlobals.navigation = navigation
3631
expect(getNavigation()).toBe(navigation)
3732
})
3833
})

0 commit comments

Comments
 (0)