Skip to content

Commit 27cf0b7

Browse files
committed
Merge branch 'main' into fix/trashbin/props
2 parents 74d9d83 + 2aa00ed commit 27cf0b7

9 files changed

Lines changed: 185 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## [2.5.1](https://github.com/nextcloud/cdav-library/compare/v2.5.0...v2.5.1) (2026-05-20)
2+
3+
4+
### Bug Fixes
5+
6+
* set dirty flag with calendar data too ([6c59076](https://github.com/nextcloud/cdav-library/commit/6c590764168cfb0972ef94d14c7bc14c53ac9208))
7+
8+
9+
110
# [2.5.0](https://github.com/nextcloud/cdav-library/compare/v2.4.0...v2.5.0) (2026-05-07)
211

312

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nextcloud/cdav-library",
3-
"version": "2.5.0",
3+
"version": "2.5.1",
44
"description": "CalDAV and CardDAV client library for Nextcloud",
55
"type": "module",
66
"main": "dist/index.cjs",

src/models/calendarTrashBin.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { DavCollection } from './davCollection.js'
1111
import * as NS from '../utility/namespaceUtility.js'
12-
import { VObject } from './vobject.js'
12+
import { DeletedCalendarObject } from './deletedCalendarObject.js'
1313
import * as XMLUtility from '../utility/xmlUtility.js'
1414

1515
export class CalendarTrashBin extends DavCollection {
@@ -20,7 +20,7 @@ export class CalendarTrashBin extends DavCollection {
2020
constructor(...args) {
2121
super(...args)
2222

23-
super._registerObjectFactory('text/calendar', VObject)
23+
super._registerObjectFactory('text/calendar', DeletedCalendarObject)
2424

2525
super._exposeProperty('retentionDuration', NS.NEXTCLOUD, 'trash-bin-retention-duration')
2626
}
@@ -31,14 +31,8 @@ export class CalendarTrashBin extends DavCollection {
3131
)
3232
skeleton.children.push({
3333
name: [NS.DAV, 'prop'],
34-
children: VObject.getPropFindList()
35-
.map((p) => ({ name: p }))
36-
.concat([
37-
{ name: [NS.NEXTCLOUD, 'calendar-uri'] },
38-
{ name: [NS.NEXTCLOUD, 'source-calendar-uri'] },
39-
{ name: [NS.NEXTCLOUD, 'calendar-owner-principal-uri'] },
40-
{ name: [NS.NEXTCLOUD, 'deleted-at'] },
41-
]),
34+
children: DeletedCalendarObject.getPropFindList()
35+
.map((p) => ({ name: p })),
4236
})
4337
skeleton.children.push({
4438
name: [NS.IETF_CALDAV, 'filter'],

src/models/davObject.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,10 @@ export class DavObject extends DAVEventListener {
258258
Object.defineProperty(this, localName, {
259259
get: () => this._props[`{${xmlNamespace}}${xmlName}`],
260260
set: (val) => {
261-
// The vCard itself is stored as `address-data` property and uses the update() method
262-
// to write the card to the server, while meta properties like the `favorite`
261+
// The contact itself is stored as `address-data` property and the calendar data is stored as `calendar-data` property.
262+
// Both use the update() method to write the data to the server, while meta properties like the `favorite`
263263
// one uses the updateProperties() method.
264-
if (xmlName === 'address-data') {
264+
if (xmlName === 'calendar-data' || xmlName === 'address-data') {
265265
this._isDirty = true
266266
}
267267

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* CDAV Library
3+
*
4+
* This library is part of the Nextcloud project
5+
*
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
import { VObject } from './vobject.js'
11+
import * as NS from '../utility/namespaceUtility.js'
12+
13+
/**
14+
* This class represents a deleted calendar object from a calendar trash bin.
15+
*
16+
* @augments VObject
17+
*/
18+
export class DeletedCalendarObject extends VObject {
19+
20+
/**
21+
* @inheritDoc
22+
*/
23+
constructor(...args) {
24+
super(...args)
25+
26+
super._exposeProperty('calendarUri', NS.NEXTCLOUD, 'calendar-uri')
27+
super._exposeProperty('sourceCalendarUri', NS.NEXTCLOUD, 'source-calendar-uri')
28+
super._exposeProperty('calendarOwnerPrincipalUri', NS.NEXTCLOUD, 'calendar-owner-principal-uri')
29+
super._exposeProperty('deletedAt', NS.NEXTCLOUD, 'deleted-at')
30+
}
31+
32+
get calendarUri() {
33+
return this._props[`{${NS.NEXTCLOUD}}calendar-uri`]
34+
}
35+
36+
get sourceCalendarUri() {
37+
return this._props[`{${NS.NEXTCLOUD}}source-calendar-uri`]
38+
}
39+
40+
get calendarOwnerPrincipalUri() {
41+
return this._props[`{${NS.NEXTCLOUD}}calendar-owner-principal-uri`]
42+
}
43+
44+
get deletedAt() {
45+
return this._props[`{${NS.NEXTCLOUD}}deleted-at`]
46+
}
47+
48+
/**
49+
* @inheritDoc
50+
*/
51+
static getPropFindList() {
52+
return super.getPropFindList().concat([
53+
[NS.NEXTCLOUD, 'calendar-uri'],
54+
[NS.NEXTCLOUD, 'source-calendar-uri'],
55+
[NS.NEXTCLOUD, 'calendar-owner-principal-uri'],
56+
[NS.NEXTCLOUD, 'deleted-at'],
57+
])
58+
}
59+
60+
}

test/unit/models/davObjectTest.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { assert, beforeEach, describe, expect, it, vi } from "vitest";
1212
import { DavObject } from "../../../src/models/davObject.js";
1313
import DAVEventListener from "../../../src/models/davEventListener.js";
1414
import NetworkRequestClientError from "../../../src/errors/networkRequestClientError.js";
15+
import * as NS from '../../../src/utility/namespaceUtility.js'
1516
import * as XMLUtility from '../../../src/utility/xmlUtility.js';
1617
import RequestMock from "../../mocks/request.mock.js";
1718
import { DavCollection as DavCollectionMock } from "../../mocks/davCollection.mock.js";
@@ -701,6 +702,27 @@ describe('Dav object model', () => {
701702
expect(davObject.url).toEqual('/foo/bar/file');
702703
});
703704

705+
it('should not mark non-data property changes as dirty', () => {
706+
const parent = new DavCollectionMock();
707+
const request = new RequestMock();
708+
const url = '/foo/bar/file';
709+
const props = {
710+
'{DAV:}getetag': '"etag foo bar tralala"',
711+
'{DAV:}getcontenttype': 'text/blub',
712+
'{DAV:}resourcetype': [],
713+
};
714+
715+
const davObject = new DavObject(parent, request, url, props, false);
716+
davObject._exposeProperty('favorite', NS.NEXTCLOUD, 'favorite', true);
717+
718+
expect(davObject.isDirty()).toEqual(false);
719+
720+
davObject.favorite = true;
721+
722+
expect(davObject.isDirty()).toEqual(false);
723+
expect(davObject._updatedProperties).toContain('{http://nextcloud.com/ns}favorite');
724+
});
725+
704726
it('should copy a DavObject into a different collection', () => {
705727
const davCollection1 = new DavCollectionMock();
706728
davCollection1.url = '/foo/bar/';
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* CDAV Library
3+
*
4+
* This library is part of the Nextcloud project
5+
*
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
import { describe, expect, it } from 'vitest'
11+
12+
import { DeletedCalendarObject } from '../../../src/models/deletedCalendarObject.js'
13+
import { VObject } from '../../../src/models/vobject.js'
14+
import RequestMock from '../../mocks/request.mock.js'
15+
import { DavCollection as DavCollectionMock } from '../../mocks/davCollection.mock.js'
16+
17+
describe('DeletedCalendarObject model', () => {
18+
19+
it('should inherit from VObject', () => {
20+
const parent = new DavCollectionMock()
21+
const request = new RequestMock()
22+
const url = '/trash-bin/objects/deleted.ics'
23+
const props = {
24+
'{DAV:}getetag': '"etag"',
25+
'{DAV:}getcontenttype': 'text/calendar',
26+
'{DAV:}resourcetype': [],
27+
'{urn:ietf:params:xml:ns:caldav}calendar-data': 'BEGIN:VCALENDAR\nEND:VCALENDAR',
28+
}
29+
30+
const object = new DeletedCalendarObject(parent, request, url, props)
31+
expect(object).toEqual(expect.any(VObject))
32+
})
33+
34+
it('should expose deleted calendar object properties', () => {
35+
const parent = new DavCollectionMock()
36+
const request = new RequestMock()
37+
const url = '/trash-bin/objects/deleted.ics'
38+
const props = {
39+
'{DAV:}getetag': '"etag"',
40+
'{DAV:}getcontenttype': 'text/calendar',
41+
'{DAV:}resourcetype': [],
42+
'{urn:ietf:params:xml:ns:caldav}calendar-data': 'BEGIN:VCALENDAR\nEND:VCALENDAR',
43+
'{http://nextcloud.com/ns}calendar-uri': 'calendar-1',
44+
'{http://nextcloud.com/ns}source-calendar-uri': 'source',
45+
'{http://nextcloud.com/ns}calendar-owner-principal-uri': 'principals/users/user',
46+
'{http://nextcloud.com/ns}deleted-at': new Date('2026-05-20T10:11:12Z'),
47+
}
48+
49+
const object = new DeletedCalendarObject(parent, request, url, props)
50+
51+
expect(object.calendarUri).toEqual('calendar-1')
52+
expect(object.sourceCalendarUri).toEqual('source')
53+
expect(object.calendarOwnerPrincipalUri).toEqual('principals/users/user')
54+
expect(object.deletedAt).toEqual(new Date('2026-05-20T10:11:12Z'))
55+
})
56+
57+
})

test/unit/models/vobjectTest.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,25 @@ describe('VObject model', () => {
4646
expect(vobject.data).toEqual('FOO BAR BLA BLUB');
4747
});
4848

49+
it('should mark calendar-data changes as dirty', () => {
50+
const parent = new DavCollectionMock();
51+
const request = new RequestMock();
52+
const url = '/foo/bar/file';
53+
const props = {
54+
'{DAV:}getetag': '"etag foo bar tralala"',
55+
'{DAV:}getcontenttype': 'text/calendar',
56+
'{DAV:}resourcetype': [],
57+
'{urn:ietf:params:xml:ns:caldav}calendar-data': 'BEGIN:VCALENDAR\nEND:VCALENDAR',
58+
};
59+
60+
const vobject = new VObject(parent, request, url, props);
61+
62+
expect(vobject.isDirty()).toEqual(false);
63+
64+
vobject.data = 'BEGIN:VCALENDAR\nVERSION:2.0\nEND:VCALENDAR';
65+
66+
expect(vobject.isDirty()).toEqual(true);
67+
expect(vobject.data).toEqual('BEGIN:VCALENDAR\nVERSION:2.0\nEND:VCALENDAR');
68+
});
69+
4970
});

0 commit comments

Comments
 (0)