Skip to content

Commit 176150a

Browse files
committed
chore: add cloning tests
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
1 parent df2a38c commit 176150a

1 file changed

Lines changed: 303 additions & 0 deletions

File tree

__tests__/files/cloning.spec.ts

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { describe, expect, test } from 'vitest'
7+
import { File, NodeData, NodeStatus } from '../../lib/node/index.ts'
8+
import { Permission } from '../../lib/permissions.ts'
9+
10+
describe('File cloning', () => {
11+
test('Clone preserves all basic properties', () => {
12+
const file = new File({
13+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
14+
mime: 'image/jpeg',
15+
owner: 'emma',
16+
mtime: new Date(Date.UTC(2023, 0, 1, 0, 0, 0)),
17+
crtime: new Date(Date.UTC(1990, 0, 1, 0, 0, 0)),
18+
size: 12345,
19+
permissions: Permission.ALL,
20+
root: '/files/emma',
21+
status: NodeStatus.NEW,
22+
})
23+
24+
const clone = file.clone()
25+
26+
expect(clone).toBeInstanceOf(File)
27+
expect(clone).not.toBe(file)
28+
expect(clone.source).toBe(file.source)
29+
expect(clone.mime).toBe(file.mime)
30+
expect(clone.owner).toBe(file.owner)
31+
expect(clone.size).toBe(file.size)
32+
expect(clone.permissions).toBe(file.permissions)
33+
expect(clone.root).toBe(file.root)
34+
expect(clone.status).toBe(file.status)
35+
expect(clone.mtime?.toISOString()).toBe(file.mtime?.toISOString())
36+
expect(clone.crtime?.toISOString()).toBe(file.crtime?.toISOString())
37+
})
38+
39+
test('Clone preserves attributes', () => {
40+
const file = new File({
41+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
42+
mime: 'image/jpeg',
43+
owner: 'emma',
44+
attributes: {
45+
favorite: true,
46+
customProp: 'value',
47+
nested: { key: 'value' },
48+
},
49+
})
50+
51+
const clone = file.clone()
52+
53+
expect(clone.attributes).toStrictEqual(file.attributes)
54+
expect(clone.attributes.favorite).toBe(true)
55+
expect(clone.attributes.customProp).toBe('value')
56+
expect(clone.attributes.nested).toStrictEqual({ key: 'value' })
57+
})
58+
59+
test('Clone is independent from original', () => {
60+
const file = new File({
61+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
62+
mime: 'image/jpeg',
63+
owner: 'emma',
64+
size: 100,
65+
attributes: {
66+
test: 'original',
67+
},
68+
})
69+
70+
const clone = file.clone()
71+
72+
// Modify the clone
73+
clone.size = 200
74+
clone.mime = 'image/png'
75+
clone.attributes.test = 'modified'
76+
clone.attributes.newProp = 'new'
77+
78+
// Original should be unchanged
79+
expect(file.size).toBe(100)
80+
expect(file.mime).toBe('image/jpeg')
81+
expect(file.attributes.test).toBe('original')
82+
expect(file.attributes.newProp).toBeUndefined()
83+
})
84+
85+
test('Clone works with minimal file', () => {
86+
const file = new File({
87+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/file.txt',
88+
owner: 'emma',
89+
})
90+
91+
const clone = file.clone()
92+
93+
expect(clone).toBeInstanceOf(File)
94+
expect(clone.source).toBe(file.source)
95+
expect(clone.mime).toBe('application/octet-stream')
96+
expect(clone.owner).toBe('emma')
97+
})
98+
99+
test('Clone works with remote file', () => {
100+
const file = new File({
101+
source: 'https://domain.com/Photos/picture.jpg',
102+
mime: 'image/jpeg',
103+
owner: null,
104+
})
105+
106+
const clone = file.clone()
107+
108+
expect(clone).toBeInstanceOf(File)
109+
expect(clone.source).toBe(file.source)
110+
expect(clone.owner).toBeNull()
111+
expect(clone.isDavResource).toBe(false)
112+
expect(clone.permissions).toBe(Permission.READ)
113+
})
114+
})
115+
116+
describe('File serialization and deserialization', () => {
117+
test('toString and JSON.parse roundtrip preserves all properties', () => {
118+
const file = new File({
119+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
120+
mime: 'image/jpeg',
121+
owner: 'emma',
122+
mtime: new Date(Date.UTC(2023, 0, 1, 0, 0, 0)),
123+
crtime: new Date(Date.UTC(1990, 0, 1, 0, 0, 0)),
124+
size: 12345,
125+
permissions: Permission.ALL,
126+
root: '/files/emma',
127+
status: NodeStatus.LOADING,
128+
})
129+
130+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
131+
const reconstructed = new File(parsed[0], parsed[1])
132+
133+
expect(reconstructed).toBeInstanceOf(File)
134+
expect(reconstructed.source).toBe(file.source)
135+
expect(reconstructed.mime).toBe(file.mime)
136+
expect(reconstructed.owner).toBe(file.owner)
137+
expect(reconstructed.size).toBe(file.size)
138+
expect(reconstructed.permissions).toBe(file.permissions)
139+
expect(reconstructed.root).toBe(file.root)
140+
expect(reconstructed.status).toBe(file.status)
141+
expect(reconstructed.mtime?.toISOString()).toBe(file.mtime?.toISOString())
142+
expect(reconstructed.crtime?.toISOString()).toBe(file.crtime?.toISOString())
143+
})
144+
145+
test('toString and JSON.parse preserves attributes', () => {
146+
const file = new File({
147+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
148+
mime: 'image/jpeg',
149+
owner: 'emma',
150+
attributes: {
151+
favorite: true,
152+
tags: ['work', 'important'],
153+
metadata: { author: 'Emma', version: 2 },
154+
},
155+
})
156+
157+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
158+
const reconstructed = new File(parsed[0], parsed[1])
159+
160+
expect(reconstructed.attributes).toStrictEqual(file.attributes)
161+
expect(reconstructed.attributes.favorite).toBe(true)
162+
expect(reconstructed.attributes.tags).toStrictEqual(['work', 'important'])
163+
expect(reconstructed.attributes.metadata).toStrictEqual({ author: 'Emma', version: 2 })
164+
})
165+
166+
test('toString and JSON.parse works with minimal file', () => {
167+
const file = new File({
168+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/file.txt',
169+
owner: 'emma',
170+
})
171+
172+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
173+
const reconstructed = new File(parsed[0], parsed[1])
174+
175+
expect(reconstructed).toBeInstanceOf(File)
176+
expect(reconstructed.source).toBe(file.source)
177+
expect(reconstructed.mime).toBe('application/octet-stream')
178+
expect(reconstructed.owner).toBe('emma')
179+
})
180+
181+
test('toString and JSON.parse is independent from original', () => {
182+
const file = new File({
183+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
184+
mime: 'image/jpeg',
185+
owner: 'emma',
186+
size: 100,
187+
attributes: {
188+
test: 'original',
189+
},
190+
})
191+
192+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
193+
const reconstructed = new File(parsed[0], parsed[1])
194+
195+
// Modify the reconstructed file
196+
reconstructed.size = 200
197+
reconstructed.mime = 'image/png'
198+
reconstructed.attributes.test = 'modified'
199+
200+
// Original should be unchanged
201+
expect(file.size).toBe(100)
202+
expect(file.mime).toBe('image/jpeg')
203+
expect(file.attributes.test).toBe('original')
204+
})
205+
206+
test('toString and JSON.parse preserves displayname', () => {
207+
const file = new File({
208+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
209+
mime: 'image/jpeg',
210+
owner: 'emma',
211+
attributes: {
212+
displayname: 'My Vacation Photo',
213+
},
214+
})
215+
216+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
217+
const reconstructed = new File(parsed[0], parsed[1])
218+
219+
expect(reconstructed.displayname).toBe('My Vacation Photo')
220+
expect(reconstructed.basename).toBe('picture.jpg')
221+
})
222+
223+
test('toString and JSON.parse works with remote file', () => {
224+
const file = new File({
225+
source: 'https://domain.com/Photos/picture.jpg',
226+
mime: 'image/jpeg',
227+
owner: null,
228+
})
229+
230+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
231+
const reconstructed = new File(parsed[0], parsed[1])
232+
233+
expect(reconstructed).toBeInstanceOf(File)
234+
expect(reconstructed.source).toBe(file.source)
235+
expect(reconstructed.owner).toBeNull()
236+
expect(reconstructed.isDavResource).toBe(false)
237+
expect(reconstructed.permissions).toBe(Permission.READ)
238+
})
239+
240+
test('toString and JSON.parse preserves all NodeStatus values', () => {
241+
const statuses = [NodeStatus.NEW, NodeStatus.FAILED, NodeStatus.LOADING, NodeStatus.LOCKED]
242+
243+
for (const status of statuses) {
244+
const file = new File({
245+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/file.txt',
246+
owner: 'emma',
247+
status,
248+
})
249+
250+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
251+
const reconstructed = new File(parsed[0], parsed[1])
252+
expect(reconstructed.status).toBe(status)
253+
}
254+
})
255+
256+
test('toString output is valid JSON', () => {
257+
const file = new File({
258+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
259+
mime: 'image/jpeg',
260+
owner: 'emma',
261+
size: 12345,
262+
})
263+
264+
const str = file.toString()
265+
expect(() => JSON.parse(str)).not.toThrow()
266+
267+
const parsed = JSON.parse(str)
268+
expect(Array.isArray(parsed)).toBe(true)
269+
expect(parsed.length).toBeGreaterThanOrEqual(1)
270+
expect(parsed[0]).toHaveProperty('source')
271+
})
272+
})
273+
274+
describe('Cloning methods comparison', () => {
275+
test('clone() and toString/parse produce equivalent files', () => {
276+
const file = new File({
277+
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
278+
mime: 'image/jpeg',
279+
owner: 'emma',
280+
mtime: new Date(Date.UTC(2023, 0, 1, 0, 0, 0)),
281+
size: 12345,
282+
permissions: Permission.ALL,
283+
root: '/files/emma',
284+
attributes: {
285+
favorite: true,
286+
tags: ['work'],
287+
},
288+
})
289+
290+
const cloned = file.clone()
291+
const parsed = JSON.parse(file.toString()) as [NodeData, RegExp?]
292+
const reconstructed = new File(parsed[0], parsed[1])
293+
294+
expect(cloned.source).toBe(reconstructed.source)
295+
expect(cloned.mime).toBe(reconstructed.mime)
296+
expect(cloned.owner).toBe(reconstructed.owner)
297+
expect(cloned.size).toBe(reconstructed.size)
298+
expect(cloned.permissions).toBe(reconstructed.permissions)
299+
expect(cloned.root).toBe(reconstructed.root)
300+
expect(cloned.mtime?.toISOString()).toBe(reconstructed.mtime?.toISOString())
301+
expect(cloned.attributes).toStrictEqual(reconstructed.attributes)
302+
})
303+
})

0 commit comments

Comments
 (0)