diff --git a/nuxeo-retention-web/elements/nuxeo-retain-button.js b/nuxeo-retention-web/elements/nuxeo-retain-button.js
index cb6d4104..e72eb0a3 100644
--- a/nuxeo-retention-web/elements/nuxeo-retain-button.js
+++ b/nuxeo-retention-web/elements/nuxeo-retain-button.js
@@ -24,6 +24,7 @@ import '@nuxeo/nuxeo-ui-elements/widgets/nuxeo-tooltip.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
+import moment from '@nuxeo/moment';
/**
`nuxeo-retain-button`
diff --git a/nuxeo-retention-web/elements/nuxeo-retention-behavior.js b/nuxeo-retention-web/elements/nuxeo-retention-behavior.js
index 0b50086c..b854b5c8 100644
--- a/nuxeo-retention-web/elements/nuxeo-retention-behavior.js
+++ b/nuxeo-retention-web/elements/nuxeo-retention-behavior.js
@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-const Nuxeo = window.Nuxeo || {};
+// eslint-disable-next-line import/prefer-default-export
+export const Nuxeo = window.Nuxeo || {};
let fetcher;
diff --git a/nuxeo-retention-web/elements/nuxeo-retention-events.js b/nuxeo-retention-web/elements/nuxeo-retention-events.js
index 76e50673..65c803db 100644
--- a/nuxeo-retention-web/elements/nuxeo-retention-events.js
+++ b/nuxeo-retention-web/elements/nuxeo-retention-events.js
@@ -27,6 +27,7 @@ import '@nuxeo/nuxeo-ui-elements/widgets/nuxeo-directory-suggestion.js';
import '@nuxeo/nuxeo-ui-elements/widgets/nuxeo-input.js';
import '@nuxeo/nuxeo-ui-elements/widgets/nuxeo-user-tag.js';
import '@polymer/paper-button/paper-button.js';
+import moment from '@nuxeo/moment';
/**
`nuxeo-retention-events`
diff --git a/nuxeo-retention-web/package.json b/nuxeo-retention-web/package.json
index 4c4a2abc..befe5eb4 100644
--- a/nuxeo-retention-web/package.json
+++ b/nuxeo-retention-web/package.json
@@ -31,6 +31,7 @@
"sinon": "^17.0.1"
},
"dependencies": {
+ "@nuxeo/moment": "^2.24.0-nx.0",
"@nuxeo/nuxeo-elements": "~3.0.2-rc.0",
"@nuxeo/nuxeo-ui-elements": "~3.0.2-rc.0",
"@polymer/paper-button": "^3.0.0",
diff --git a/nuxeo-retention-web/test/nuxeo-hold-toggle-button.test.js b/nuxeo-retention-web/test/nuxeo-hold-toggle-button.test.js
new file mode 100644
index 00000000..14a44fa0
--- /dev/null
+++ b/nuxeo-retention-web/test/nuxeo-hold-toggle-button.test.js
@@ -0,0 +1,341 @@
+/**
+@license
+©2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use attachEl file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import { fixture, html } from '@nuxeo/testing-helpers';
+import '../elements/nuxeo-hold-toggle-button.js';
+import sinon from 'sinon';
+import { expect } from 'chai';
+
+const document = {
+ 'entity-type': 'document',
+ contextParameters: {
+ attachEl: {
+ entries: [
+ {
+ path: '/default-domain',
+ title: 'Domain',
+ type: 'Domain',
+ uid: '1',
+ },
+ {
+ path: '/default-domain/workspaces',
+ title: 'Workspaces',
+ type: 'WorkspaceRoot',
+ uid: '2',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace',
+ title: 'my workspace',
+ type: 'Workspace',
+ uid: '3',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1',
+ title: 'folder 1',
+ type: 'Folder',
+ uid: '4',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2',
+ title: 'folder 2',
+ type: 'Folder',
+ uid: '5',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3',
+ title: 'folder 3',
+ type: 'Folder',
+ uid: '6',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+ },
+ ],
+ },
+ },
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+};
+
+window.nuxeo.I18n.language = 'en';
+window.nuxeo.I18n.en = window.nuxeo.I18n.en || {};
+window.nuxeo.I18n.en['retention.holdToggleButton.tooltip.hold'] = 'Legal Hold';
+window.nuxeo.I18n.en['retention.holdToggleButton.tooltip.unhold'] = 'Unhold';
+window.nuxeo.I18n.en['retention.holdToggleButton.bulk.hold.poll'] = 'Setting legal hold';
+window.nuxeo.I18n.en['retention.holdToggleButton.bulk.hold'] = 'Legal hold set';
+window.nuxeo.I18n.en['retention.holdToggleButton.bulk.unhold'] = 'Legal hold unset';
+window.nuxeo.I18n.en['retention.holdToggleButton.bulk.unhold.poll'] = 'Unsetting legal hold';
+
+suite('nuxeo-hold-toggle-button', () => {
+ let attachEl;
+
+ setup(async () => {
+ attachEl = await fixture(html` `);
+ });
+
+ suite('test _isAvailable', () => {
+ test('Should return provider object if provider is available', async () => {
+ const providerObj = {
+ provider: true,
+ };
+ attachEl.provider = providerObj;
+ sinon.stub(attachEl, 'canSetLegalHold').returns(true);
+ expect(attachEl._isAvailable()).equal(providerObj);
+ });
+
+ test('Should return true if provider is not available and canSetLegalHold permission is present', async () => {
+ attachEl.provider = null;
+ sinon.stub(attachEl, 'canSetLegalHold').returns(true);
+ expect(attachEl._isAvailable()).equal(true);
+ });
+
+ test('Should return false if provider is not available and canSetLegalHold permission is not present', async () => {
+ attachEl.provider = null;
+ sinon.stub(attachEl, 'canSetLegalHold').returns(false);
+ expect(attachEl._isAvailable()).equal(false);
+ });
+ });
+
+ suite('test _hold', () => {
+ const windowStub = sinon.stub(window, 'confirm');
+ test('Should execute Bulk.RunAction if user confirms to put document under legal hold and provider is present', async () => {
+ const providerObj = {
+ provider: true,
+ };
+ attachEl.provider = providerObj;
+ attachEl.document.isFlexibleRecord = true;
+ attachEl.document.isUnderRetentionOrLegalHold = true;
+ attachEl.description = 'some text';
+ windowStub.returns(true);
+ sinon.spy(attachEl, '_toggleDialog');
+ sinon.stub(attachEl.$.opHold, 'execute').resolves();
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._hold();
+ expect(attachEl.$.opHold.op).equal('Bulk.RunAction');
+ expect(attachEl.$.opHold.input).equal(providerObj);
+ expect(attachEl.$.opHold.async).equal(true);
+ setTimeout(() => {
+ expect(attachEl._toggleDialog.calledOnce).to.equal(true);
+ }, 0);
+ });
+
+ test('Should execute Document.Hold if user confirms to put document under legal hold and provider is not present', async () => {
+ attachEl.provider = null;
+ attachEl.document.isFlexibleRecord = true;
+ attachEl.document.isUnderRetentionOrLegalHold = true;
+ attachEl.description = 'some text';
+ sinon.spy(attachEl, '_toggleDialog');
+ sinon.stub(attachEl.$.opHold, 'execute').resolves();
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._hold();
+ expect(attachEl.$.opHold.op).equal('Document.Hold');
+ expect(attachEl.$.opHold.input).equal(attachEl.document);
+ expect(attachEl.$.opHold.async).equal(false);
+ expect(attachEl.$.opHold.params).to.deep.equal({ description: 'some text' });
+ setTimeout(() => {
+ expect(attachEl._toggleDialog.calledOnce).to.equal(true);
+ expect(attachEl.dispatchEvent.calledOnce).to.equal(true);
+ }, 0);
+ });
+
+ test('Should not execute either Bulk.RunAction or Document.Hold if user cancels legal hold on document', async () => {
+ sinon.spy(attachEl, '_toggleDialog');
+ sinon.stub(attachEl.$.opHold, 'execute');
+ attachEl.document.isFlexibleRecord = true;
+ attachEl.document.isUnderRetentionOrLegalHold = true;
+ windowStub.returns(false);
+ attachEl._hold();
+ expect(attachEl._toggleDialog.calledOnce).to.equal(false);
+ expect(attachEl.$.opHold.execute.calledOnce).to.equal(false);
+ });
+ });
+
+ suite('test _unhold', () => {
+ test('Should execute Bulk.RunAction if provider is present', async () => {
+ const providerObj = {
+ provider: true,
+ };
+ attachEl.provider = providerObj;
+ sinon.stub(attachEl.$.opUnhold, 'execute');
+ attachEl._unhold();
+ expect(attachEl.$.opUnhold.op).equal('Bulk.RunAction');
+ expect(attachEl.$.opUnhold.input).equal(providerObj);
+ expect(attachEl.$.opUnhold.async).equal(true);
+ expect(attachEl.$.opUnhold.params).to.deep.equal({ action: 'unholdDocumentsAction' });
+ expect(attachEl.$.opUnhold.execute.calledOnce).to.equal(true);
+ });
+
+ test('Should execute Document.Unhold if provider is not present', async () => {
+ attachEl.provider = null;
+ sinon.stub(attachEl.$.opUnhold, 'execute').resolves();
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._unhold();
+ expect(attachEl.$.opUnhold.op).equal('Document.Unhold');
+ expect(attachEl.$.opUnhold.input).equal(attachEl.document);
+ expect(attachEl.$.opUnhold.async).equal(false);
+ expect(attachEl.$.opUnhold.params).to.deep.equal({});
+ setTimeout(() => {
+ expect(attachEl.dispatchEvent.calledOnce).to.equal(true);
+ }, 0);
+ });
+ });
+
+ suite('test _unhold', () => {
+ test('Should toggle dialog if hold is set to false', async () => {
+ attachEl.hold = false;
+ sinon.spy(attachEl, '_toggleDialog');
+ attachEl._toggle();
+ expect(attachEl._toggleDialog.calledOnce).to.equal(true);
+ });
+
+ test('Should perform unhold operation if hold is set to true', async () => {
+ attachEl.hold = true;
+ sinon.spy(attachEl, '_unhold');
+ attachEl._toggle();
+ expect(attachEl._unhold.calledOnce).to.equal(true);
+ });
+ });
+
+ suite('test _toggleDialog', () => {
+ test('Should reset popup and toggle dialog', async () => {
+ sinon.spy(attachEl, '_resetPopup');
+ sinon.stub(attachEl.$.dialog, 'toggle');
+ attachEl._toggleDialog();
+ expect(attachEl._resetPopup.calledOnce).to.equal(true);
+ expect(attachEl.$.dialog.toggle.calledOnce).to.equal(true);
+ });
+ });
+
+ suite('test _resetPopup', () => {
+ test('Should set description to null', async () => {
+ sinon.spy(attachEl, 'set');
+ attachEl._resetPopup();
+ expect(attachEl.set.calledWith('description', null)).to.equal(true);
+ });
+ });
+
+ suite('test _computeTooltip', () => {
+ test('Should return tooltip text for hold', async () => {
+ attachEl.hold = true;
+ expect(attachEl._computeTooltip()).equal('Unhold');
+ });
+
+ test('Should return tooltip text for unhold', async () => {
+ attachEl.hold = false;
+ expect(attachEl._computeTooltip()).equal('Legal Hold');
+ });
+ });
+
+ suite('test _computeLabel', () => {
+ test('Should return label for hold', async () => {
+ attachEl.hold = true;
+ expect(attachEl._computeLabel()).equal('Unhold');
+ });
+
+ test('Should return label for unhold', async () => {
+ attachEl.hold = false;
+ expect(attachEl._computeLabel()).equal('Legal Hold');
+ });
+ });
+
+ suite('test _computeIcon', () => {
+ test('Should return label for hold', async () => {
+ attachEl.hold = true;
+ expect(attachEl._computeIcon()).equal('nuxeo:hold');
+ });
+
+ test('Should return label for unhold', async () => {
+ attachEl.hold = false;
+ expect(attachEl._computeIcon()).equal('nuxeo:unhold');
+ });
+ });
+
+ suite('test _documentChanged', () => {
+ test('Should set hold property to true if document is present and has legal hold', async () => {
+ attachEl.document = document;
+ attachEl.document.hasLegalHold = true;
+ attachEl._documentChanged();
+ expect(attachEl.hold).equal(true);
+ });
+
+ test('Should set hold property to true if document is present but does not have legal hold', async () => {
+ attachEl.document = document;
+ attachEl.document.hasLegalHold = false;
+ attachEl._documentChanged();
+ expect(attachEl.hold).equal(false);
+ });
+ });
+
+ suite('test _onHoldPollStart', () => {
+ test('Should dispatch notify event', async () => {
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._onHoldPollStart();
+ expect(
+ attachEl.dispatchEvent.calledWith(
+ new CustomEvent('notify', {
+ composed: true,
+ bubbles: true,
+ detail: { message: 'Setting legal hold' },
+ }),
+ ),
+ ).to.equal(true);
+ });
+ });
+
+ suite('test _onUnholdPollStart', () => {
+ test('Should dispatch notify event', async () => {
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._onUnholdPollStart();
+ expect(
+ attachEl.dispatchEvent.calledWith(
+ new CustomEvent('notify', {
+ composed: true,
+ bubbles: true,
+ detail: { message: 'Unsetting legal hold' },
+ }),
+ ),
+ ).to.equal(true);
+ });
+ });
+
+ suite('test _onHoldResponse', () => {
+ test('Should dispatch notify and refresh event', async () => {
+ sinon.stub(attachEl.$.waitEs, 'execute').resolves();
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._onHoldResponse();
+ setTimeout(() => {
+ expect(attachEl.dispatchEvent.calledTwice).to.equal(true);
+ }, 0);
+ });
+ });
+
+ suite('test _onUnholdResponse', () => {
+ test('Should dispatch notify and refresh event', async () => {
+ sinon.stub(attachEl.$.waitEs, 'execute').resolves();
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._onUnholdResponse();
+ setTimeout(() => {
+ expect(attachEl.dispatchEvent.calledTwice).to.equal(true);
+ }, 0);
+ });
+ });
+});
diff --git a/nuxeo-retention-web/test/nuxeo-retain-button.test.js b/nuxeo-retention-web/test/nuxeo-retain-button.test.js
new file mode 100644
index 00000000..5acce481
--- /dev/null
+++ b/nuxeo-retention-web/test/nuxeo-retain-button.test.js
@@ -0,0 +1,188 @@
+/**
+@license
+©2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use attachEl file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import { fixture, html } from '@nuxeo/testing-helpers';
+import moment from '@nuxeo/moment';
+import '../elements/nuxeo-retain-button.js';
+import sinon from 'sinon';
+import { expect } from 'chai';
+
+const document = {
+ 'entity-type': 'document',
+ contextParameters: {
+ attachEl: {
+ entries: [
+ {
+ path: '/default-domain',
+ title: 'Domain',
+ type: 'Domain',
+ uid: '1',
+ },
+ {
+ path: '/default-domain/workspaces',
+ title: 'Workspaces',
+ type: 'WorkspaceRoot',
+ uid: '2',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace',
+ title: 'my workspace',
+ type: 'Workspace',
+ uid: '3',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1',
+ title: 'folder 1',
+ type: 'Folder',
+ uid: '4',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2',
+ title: 'folder 2',
+ type: 'Folder',
+ uid: '5',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3',
+ title: 'folder 3',
+ type: 'Folder',
+ uid: '6',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+ },
+ ],
+ },
+ },
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+};
+
+window.nuxeo.I18n.language = 'en';
+window.nuxeo.I18n.en = window.nuxeo.I18n.en || {};
+window.nuxeo.I18n.en['retention.action.retain'] = 'Extend retention';
+
+suite('nuxeo-retain-button', () => {
+ let attachEl;
+
+ setup(async () => {
+ attachEl = await fixture(html` `);
+ });
+
+ suite('test _isAvailable', () => {
+ test('Should return true if canSetRetention permission is available', async () => {
+ sinon.stub(attachEl, 'canSetRetention').returns(true);
+ expect(attachEl._isAvailable()).equal(true);
+ });
+
+ test('Should return false if canSetRetention permission is not available', async () => {
+ sinon.stub(attachEl, 'canSetRetention').returns(false);
+ expect(attachEl._isAvailable()).equal(false);
+ });
+ });
+
+ suite('test _computeLabel', () => {
+ test('Should return i18n label for extend retention button', async () => {
+ expect(attachEl._computeLabel()).equal('Extend retention');
+ });
+ });
+
+ suite('test _toggleDialog', () => {
+ test('Should toggle dialog', async () => {
+ sinon.stub(attachEl.$.dialog, 'toggle');
+ attachEl._toggleDialog();
+ expect(attachEl.$.dialog.toggle.calledOnce).to.equal(true);
+ });
+ });
+
+ suite('test _retain', () => {
+ test('Should toggle dialog', async () => {
+ sinon.stub(attachEl.$.retainOp, 'execute').resolves();
+ sinon.spy(attachEl, '_toggleDialog');
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl.until = '2025-05-09';
+ attachEl._retain();
+ expect(attachEl.$.retainOp.params).to.deep.equal({ until: '2025-05-09' });
+ setTimeout(() => {
+ expect(attachEl.dispatchEvent.calledOnce).to.equal(true);
+ expect(attachEl._toggleDialog.calledOnce).to.equal(true);
+ }, 0);
+ });
+ });
+
+ suite('test _computeMinDate', () => {
+ setup(async () => {
+ sinon.spy(attachEl, 'set');
+ });
+ test('Should return min date as retain until date of document if document has retain until date & retention date is not indeterminate', async () => {
+ attachEl.document.retainUntil = '2025-05-09';
+ sinon.stub(attachEl, 'isRetentionDateIndeterminate').returns(false);
+ expect(attachEl._computeMinDate()).equal('2025-05-09');
+ expect(attachEl.set.calledWith('until', '2025-05-09')).to.equal(true);
+ });
+
+ test("Should return min date as tomorrow's date if document has retain until date but retention date is indeterminate", async () => {
+ attachEl.document.retainUntil = '2025-05-09';
+ const tomorrow = moment().add(1, 'days');
+ sinon.stub(attachEl, 'isRetentionDateIndeterminate').returns(true);
+ expect(attachEl._computeMinDate()).equal(moment(tomorrow.toJSON()).format('YYYY-MM-DD'));
+ expect(attachEl.set.calledWith('until', undefined)).to.equal(true);
+ });
+
+ test("Should return min date as tomorrow's date if document does not have retain until date though retention date is not indeterminate", async () => {
+ attachEl.document.retainUntil = null;
+ const tomorrow = moment().add(1, 'days');
+ sinon.stub(attachEl, 'isRetentionDateIndeterminate').returns(false);
+ expect(attachEl._computeMinDate()).equal(moment(tomorrow.toJSON()).format('YYYY-MM-DD'));
+ expect(attachEl.set.calledWith('until', undefined)).to.equal(true);
+ });
+
+ test("Should return min date as tomorrow's date if document does not have retain until date and retention date is indeterminate", async () => {
+ attachEl.document.retainUntil = null;
+ const tomorrow = moment().add(1, 'days');
+ sinon.stub(attachEl, 'isRetentionDateIndeterminate').returns(true);
+ expect(attachEl._computeMinDate()).equal(moment(tomorrow.toJSON()).format('YYYY-MM-DD'));
+ expect(attachEl.set.calledWith('until', undefined)).to.equal(true);
+ });
+ });
+
+ suite('test _isValid', () => {
+ test('Should return true if document is available & retainUntil property is valid in document', async () => {
+ attachEl.document = document;
+ attachEl.document.retainUntil = '2025-05-09';
+ attachEl.until = '2025-05-09';
+ expect(attachEl._isValid()).equal(true);
+ });
+
+ test('Should return false if document is available & retainUntil property is valid in document but until is undefined', async () => {
+ attachEl.document = document;
+ attachEl.document.retainUntil = '2025-05-09';
+ expect(attachEl._isValid()).equal(false);
+ });
+
+ test('Should return true if document is available but retainUntil property is not available in document', async () => {
+ attachEl.document = document;
+ attachEl.until = '2025-05-09';
+ expect(attachEl._isValid()).equal(true);
+ });
+ });
+});
diff --git a/nuxeo-retention-web/test/nuxeo-retention-behavior.test.js b/nuxeo-retention-web/test/nuxeo-retention-behavior.test.js
new file mode 100644
index 00000000..cc6829fd
--- /dev/null
+++ b/nuxeo-retention-web/test/nuxeo-retention-behavior.test.js
@@ -0,0 +1,271 @@
+/**
+@license
+©2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use retentionBehaviorInstance file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import sinon from 'sinon';
+import { expect } from 'chai';
+import { Nuxeo } from '../elements/nuxeo-retention-behavior.js';
+
+suite('nuxeo-retention-behavior', () => {
+ const document = {
+ 'entity-type': 'document',
+ contextParameters: {
+ attachEl: {
+ entries: [
+ {
+ path: '/default-domain',
+ title: 'Domain',
+ type: 'Domain',
+ uid: '1',
+ },
+ {
+ path: '/default-domain/workspaces',
+ title: 'Workspaces',
+ type: 'WorkspaceRoot',
+ uid: '2',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace',
+ title: 'my workspace',
+ type: 'Workspace',
+ uid: '3',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1',
+ title: 'folder 1',
+ type: 'Folder',
+ uid: '4',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2',
+ title: 'folder 2',
+ type: 'Folder',
+ uid: '5',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3',
+ title: 'folder 3',
+ type: 'Folder',
+ uid: '6',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+ },
+ ],
+ },
+ },
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+ properties: {},
+ };
+ let sandbox;
+ let retentionBehaviorInstance;
+ const originalRetentionBehaviorObj = Nuxeo.RetentionBehavior;
+
+ setup(async () => {
+ retentionBehaviorInstance = Object.create(Nuxeo.RetentionBehavior);
+ sandbox = sinon.createSandbox();
+ });
+
+ teardown(() => {
+ Nuxeo.RetentionBehavior = originalRetentionBehaviorObj;
+ sandbox.restore();
+ });
+
+ suite('test _isAuto', () => {
+ test('Should return true if document is available & document.properties is available & document.properties["retention_rule:applicationPolicy"] = auto', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_rule:applicationPolicy'] = 'auto';
+ expect(retentionBehaviorInstance._isAuto()).equal(true);
+ });
+
+ test('Should return false if document is available & document.properties is available but document.properties["retention_rule:applicationPolicy"] != auto', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_rule:applicationPolicy'] = 'custom';
+ expect(retentionBehaviorInstance._isAuto()).equal(false);
+ });
+
+ test('Should return false if document is available but document.properties is not available', async () => {
+ retentionBehaviorInstance.document = document;
+ expect(retentionBehaviorInstance._isAuto()).equal(false);
+ });
+
+ test('Should return false if document is not available', async () => {
+ retentionBehaviorInstance.document = {};
+ expect(retentionBehaviorInstance._isAuto()).equal(undefined);
+ });
+ });
+
+ suite('test _isImmediate', () => {
+ test('Should return true if document is available & document.properties is available & document.properties["retention_def:startingPointPolicy"] = immediate', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'immediate';
+ expect(retentionBehaviorInstance._isImmediate()).equal(true);
+ });
+
+ test('Should return false if document is available & document.properties is available but document.properties["retention_def:startingPointPolicy"] != immediate', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'custom';
+ expect(retentionBehaviorInstance._isImmediate()).equal(false);
+ });
+
+ test('Should return false if document is available but document.properties is not available', async () => {
+ retentionBehaviorInstance.document = document;
+ expect(retentionBehaviorInstance._isImmediate()).equal(false);
+ });
+
+ test('Should return false if document is not available', async () => {
+ retentionBehaviorInstance.document = {};
+ expect(retentionBehaviorInstance._isImmediate()).equal(undefined);
+ });
+ });
+
+ suite('test _isEventBased', () => {
+ test('Should return true if document is available & document.properties is available & document.properties["retention_def:startingPointPolicy"] = event_based', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'event_based';
+ expect(retentionBehaviorInstance._isEventBased()).equal(true);
+ });
+
+ test('Should return false if document is available & document.properties is available but document.properties["retention_def:startingPointPolicy"] != event_based', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'custom';
+ expect(retentionBehaviorInstance._isEventBased()).equal(false);
+ });
+
+ test('Should return false if document is available but document.properties is not available', async () => {
+ retentionBehaviorInstance.document = document;
+ expect(retentionBehaviorInstance._isEventBased()).equal(false);
+ });
+
+ test('Should return false if document is not available', async () => {
+ retentionBehaviorInstance.document = {};
+ expect(retentionBehaviorInstance._isEventBased()).equal(undefined);
+ });
+ });
+
+ suite('test _isMetadataBased', () => {
+ test('Should return true if document is available & document.properties is available & document.properties["retention_def:startingPointPolicy"] = metadata_based', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'metadata_based';
+ expect(retentionBehaviorInstance._isMetadataBased()).equal(true);
+ });
+
+ test('Should return false if document is available & document.properties is available but document.properties["retention_def:startingPointPolicy"] != metadata_based', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'custom';
+ expect(retentionBehaviorInstance._isMetadataBased()).equal(false);
+ });
+
+ test('Should return false if document is available but document.properties is not available', async () => {
+ retentionBehaviorInstance.document = document;
+ expect(retentionBehaviorInstance._isMetadataBased()).equal(false);
+ });
+
+ test('Should return false if document is not available', async () => {
+ retentionBehaviorInstance.document = {};
+ expect(retentionBehaviorInstance._isMetadataBased()).equal(undefined);
+ });
+ });
+
+ suite('test _isAfterDelay', () => {
+ test('Should return true if document is available & document.properties is available & document.properties["retention_def:startingPointPolicy"] = after_delay', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'after_delay';
+ expect(retentionBehaviorInstance._isAfterDelay()).equal(true);
+ });
+
+ test('Should return false if document is available & document.properties is available but document.properties["retention_def:startingPointPolicy"] != after_delay', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_def:startingPointPolicy'] = 'custom';
+ expect(retentionBehaviorInstance._isAfterDelay()).equal(false);
+ });
+
+ test('Should return false if document is available but document.properties is not available', async () => {
+ retentionBehaviorInstance.document = document;
+ expect(retentionBehaviorInstance._isAfterDelay()).equal(false);
+ });
+
+ test('Should return false if document is not available', async () => {
+ retentionBehaviorInstance.document = {};
+ expect(retentionBehaviorInstance._isAfterDelay()).equal(undefined);
+ });
+ });
+
+ suite('test _isManual', () => {
+ test('Should return true if document is available & document.properties is available & document.properties["retention_rule:applicationPolicy"] = manual', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_rule:applicationPolicy'] = 'manual';
+ expect(retentionBehaviorInstance._isManual()).equal(true);
+ });
+
+ test('Should return false if document is available & document.properties is available but document.properties["retention_rule:applicationPolicy"] != manual', async () => {
+ retentionBehaviorInstance.document = document;
+ retentionBehaviorInstance.document.properties['retention_rule:applicationPolicy'] = 'custom';
+ expect(retentionBehaviorInstance._isManual()).equal(false);
+ });
+
+ test('Should return false if document is available but document.properties is not available', async () => {
+ retentionBehaviorInstance.document = document;
+ expect(retentionBehaviorInstance._isManual()).equal(false);
+ });
+
+ test('Should return false if document is not available', async () => {
+ retentionBehaviorInstance.document = {};
+ expect(retentionBehaviorInstance._isManual()).equal(undefined);
+ });
+ });
+
+ suite('test _computeApplicationPolicyLabel', () => {
+ test('Should return empty string if document is not available', async () => {
+ expect(retentionBehaviorInstance._computeApplicationPolicyLabel()).equal('');
+ });
+ });
+
+ suite('test _computeStartPolicyLabel', () => {
+ test('Should return empty string if document is not available', async () => {
+ expect(retentionBehaviorInstance._computeStartPolicyLabel()).equal('');
+ });
+ });
+
+ suite('test ready', () => {
+ test('should set docTypes and dateFields when fetcher resolves', async () => {
+ const resObj = Promise.resolve({
+ docTypes: [
+ {
+ doc: {
+ schemas: ['file', 'folder', 'picture'],
+ facets: ['record'],
+ },
+ },
+ ],
+ });
+
+ sandbox.stub(window.document, 'createElement').returns({ get: sandbox.stub().resolves(resObj) });
+ sandbox.stub(window.document.body, 'appendChild');
+ sandbox.stub(window.document.body, 'removeChild');
+ await retentionBehaviorInstance.ready();
+ expect(retentionBehaviorInstance.properties.docTypes).to.be.an.instanceof(Function);
+ expect(retentionBehaviorInstance.properties.dateFields).to.be.an.instanceof(Function);
+ });
+ });
+});
diff --git a/nuxeo-retention-web/test/nuxeo-retention-events.test.js b/nuxeo-retention-web/test/nuxeo-retention-events.test.js
new file mode 100644
index 00000000..3de972e2
--- /dev/null
+++ b/nuxeo-retention-web/test/nuxeo-retention-events.test.js
@@ -0,0 +1,223 @@
+/**
+@license
+©2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use attachEl file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import { fixture, html } from '@nuxeo/testing-helpers';
+import '../elements/nuxeo-retention-events.js';
+import sinon from 'sinon';
+import { expect } from 'chai';
+
+const document = {
+ 'entity-type': 'document',
+ contextParameters: {
+ attachEl: {
+ entries: [
+ {
+ path: '/default-domain',
+ title: 'Domain',
+ type: 'Domain',
+ uid: '1',
+ },
+ {
+ path: '/default-domain/workspaces',
+ title: 'Workspaces',
+ type: 'WorkspaceRoot',
+ uid: '2',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace',
+ title: 'my workspace',
+ type: 'Workspace',
+ uid: '3',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1',
+ title: 'folder 1',
+ type: 'Folder',
+ uid: '4',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2',
+ title: 'folder 2',
+ type: 'Folder',
+ uid: '5',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3',
+ title: 'folder 3',
+ type: 'Folder',
+ uid: '6',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+ },
+ ],
+ },
+ },
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+};
+
+window.nuxeo.I18n.language = 'en';
+window.nuxeo.I18n.en = window.nuxeo.I18n.en || {};
+window.nuxeo.I18n.en['retention.events.empty'] = 'No past events';
+window.nuxeo.I18n.en['retention.events.fired.success'] = 'Event successfully fired';
+
+suite('nuxeo-retention-events', () => {
+ let attachEl;
+
+ setup(async () => {
+ attachEl = await fixture(html` `);
+ });
+
+ suite('test _observeStartDate', () => {
+ test('Should set valid start and end date & referesh history if start date & end date are available', () => {
+ sinon.spy(attachEl, '_refreshHistory');
+ attachEl.startDate = '2024-02-20';
+ attachEl._observeStartDate();
+ expect(attachEl.$.provider.params.startDate).equal('2024-02-20');
+ expect(attachEl._refreshHistory.calledTwice).to.equal(true);
+ });
+
+ test('Should delete provider start date if it is available & refresh history', async () => {
+ attachEl.$.provider.params.startDate = '2024-02-19';
+ sinon.spy(attachEl, '_refreshHistory');
+ attachEl._observeStartDate();
+ expect(attachEl.$.provider.params.startDate).equal(undefined);
+ expect(attachEl._refreshHistory.calledOnce).to.equal(true);
+ });
+ });
+
+ suite('test _observeEndDate', () => {
+ test('Should set valid start and end date & referesh history if start date & end date are available', () => {
+ sinon.spy(attachEl, '_refreshHistory');
+ attachEl.endDate = '2024-02-20';
+ attachEl._observeEndDate();
+ expect(attachEl.$.provider.params.endDate).equal('2024-02-20');
+ expect(attachEl._refreshHistory.calledTwice).to.equal(true);
+ });
+
+ test('Should delete provider start date if it is available & refresh history', async () => {
+ attachEl.$.provider.params.endDate = '2024-02-19';
+ sinon.spy(attachEl, '_refreshHistory');
+ attachEl._observeEndDate();
+ expect(attachEl.$.provider.params.endDate).equal(undefined);
+ expect(attachEl._refreshHistory.calledOnce).to.equal(true);
+ });
+ });
+
+ suite('test _refreshHistory', () => {
+ test('Should fetch retention events table if visible is true', async () => {
+ attachEl.visible = true;
+ sinon.stub(attachEl.$.table, 'reset');
+ sinon.stub(attachEl.$.table, 'fetch').resolves();
+ attachEl._refreshHistory(100);
+ expect(attachEl.$.provider.page).equal(1);
+ expect(attachEl.$.table.reset.calledOnce).to.equal(true);
+ setTimeout(() => {
+ expect(attachEl.$.table.emptyLabel).equal('No past events');
+ }, 100);
+ });
+
+ test('Should not fetch retention events table if visible is false', async () => {
+ attachEl.visible = false;
+ sinon.stub(attachEl.$.table, 'reset');
+ sinon.stub(attachEl.$.table, 'fetch').resolves();
+ attachEl._refreshHistory(100);
+ expect(attachEl.$.table.reset.calledOnce).to.equal(false);
+ setTimeout(() => {
+ expect(attachEl.$.table.emptyLabel).equal('No past events');
+ }, 100);
+ });
+ });
+
+ suite('test _fire', () => {
+ test('Should dispatch notify event and referesh history when operation is executed', async () => {
+ sinon.stub(attachEl.$.op, 'execute').resolves();
+ sinon.spy(attachEl, 'dispatchEvent');
+ sinon.spy(attachEl, '_refreshHistory');
+ attachEl._event = 'fire';
+ attachEl._eventInput = 'fire-input';
+ attachEl._fire();
+ expect(attachEl.$.op.params).to.deep.equal({ name: 'fire' });
+ expect(attachEl.$.op.input).equal('fire-input');
+ setTimeout(() => {
+ expect(attachEl.dispatchEvent.calledOnce).to.equal(true);
+ expect(attachEl._event).equal(null);
+ expect(attachEl._eventInput).equal(null);
+ expect(attachEl._refreshHistory.calledWith(100)).to.equal(false);
+ }, 0);
+ });
+ });
+
+ suite('test _canFire', () => {
+ test('Should return true if firingEvent = false & _event is valid', async () => {
+ attachEl.firingEvent = false;
+ attachEl._event = 'some event';
+ expect(attachEl._canFire()).equal(true);
+ });
+
+ test('Should return false if firingEvent = true & _event is valid', async () => {
+ attachEl.firingEvent = true;
+ attachEl._event = 'some event';
+ expect(attachEl._canFire()).equal(false);
+ });
+
+ test('Should return false if firingEvent = false & _event is invalid', async () => {
+ attachEl.firingEvent = false;
+ attachEl._event = '';
+ expect(attachEl._canFire()).equal(false);
+ });
+
+ test('Should return false if firingEvent = true & _event is invalid', async () => {
+ attachEl.firingEvent = true;
+ attachEl._event = '';
+ expect(attachEl._canFire()).equal(false);
+ });
+ });
+
+ suite('test _filterEvents', () => {
+ test('Should return true if evt is valid & evt.id is present & evt.id has the string "Retention."', async () => {
+ const evt = {
+ id: 'Retention.AttachRule',
+ };
+ expect(attachEl._filterEvents(evt)).equal(true);
+ });
+
+ test('Should return false if evt is valid & evt.id is present but evt.id does not have the string "Retention."', async () => {
+ const evt = {
+ id: 'label',
+ };
+ expect(attachEl._filterEvents(evt)).equal(false);
+ });
+
+ test('Should return false if evt is valid but evt.id is not present', async () => {
+ const evt = {
+ label: 'Retention',
+ };
+ expect(attachEl._filterEvents(evt)).equal(undefined);
+ });
+
+ test('Should return false if evt is invalid', async () => {
+ expect(attachEl._filterEvents()).equal(undefined);
+ });
+ });
+});
diff --git a/nuxeo-retention-web/test/nuxeo-unattach-rule-button.test.js b/nuxeo-retention-web/test/nuxeo-unattach-rule-button.test.js
new file mode 100644
index 00000000..1f1fc6ff
--- /dev/null
+++ b/nuxeo-retention-web/test/nuxeo-unattach-rule-button.test.js
@@ -0,0 +1,159 @@
+/**
+@license
+©2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use attachEl file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import { fixture, html } from '@nuxeo/testing-helpers';
+import '../elements/nuxeo-unattach-rule-button.js';
+import sinon from 'sinon';
+import { expect } from 'chai';
+
+const document = {
+ 'entity-type': 'document',
+ contextParameters: {
+ attachEl: {
+ entries: [
+ {
+ path: '/default-domain',
+ title: 'Domain',
+ type: 'Domain',
+ uid: '1',
+ },
+ {
+ path: '/default-domain/workspaces',
+ title: 'Workspaces',
+ type: 'WorkspaceRoot',
+ uid: '2',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace',
+ title: 'my workspace',
+ type: 'Workspace',
+ uid: '3',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1',
+ title: 'folder 1',
+ type: 'Folder',
+ uid: '4',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2',
+ title: 'folder 2',
+ type: 'Folder',
+ uid: '5',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3',
+ title: 'folder 3',
+ type: 'Folder',
+ uid: '6',
+ },
+ {
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+ },
+ ],
+ },
+ },
+ path: '/default-domain/workspaces/my workspace/folder 1/folder 2/folder 3/my file',
+ title: 'my file',
+ type: 'File',
+ uid: '7',
+};
+
+window.nuxeo.I18n.language = 'en';
+window.nuxeo.I18n.en = window.nuxeo.I18n.en || {};
+window.nuxeo.I18n.en['retention.rule.label.undeclare'] = 'Undeclare Record';
+window.nuxeo.I18n.en['retention.rule.label.undeclared.notify'] = 'Record undeclared';
+
+suite('nuxeo-unattach-rule-button', () => {
+ let attachEl;
+
+ setup(async () => {
+ attachEl = await fixture(html` `);
+ });
+
+ suite('test _isAvailable', () => {
+ test('Should return true if document is available & isFlexibleRecord property is true & hasFacet = true & has WriteProperties & UnsetRetention permission', async () => {
+ const doc = document;
+ doc.isFlexibleRecord = true;
+ const permissionStub = sinon.stub(attachEl, 'hasPermission');
+ sinon.stub(attachEl, 'hasFacet').returns(true);
+ permissionStub.withArgs(doc, 'WriteProperties').returns(true);
+ permissionStub.withArgs(doc, 'UnsetRetention').returns(true);
+ expect(attachEl._isAvailable(doc)).equal(true);
+ });
+
+ test('Should return false if document is available & isFlexibleRecord property is true & hasFacet = true & has WriteProperties but no UnsetRetention permission', async () => {
+ const doc = document;
+ doc.isFlexibleRecord = true;
+ const permissionStub = sinon.stub(attachEl, 'hasPermission');
+ sinon.stub(attachEl, 'hasFacet').returns(true);
+ permissionStub.withArgs(doc, 'WriteProperties').returns(true);
+ permissionStub.withArgs(doc, 'UnsetRetention').returns(false);
+ expect(attachEl._isAvailable(doc)).equal(false);
+ });
+
+ test('Should return false if document is available & isFlexibleRecord property is true & hasFacet = true but no WriteProperties & UnsetRetention permission', async () => {
+ const doc = document;
+ doc.isFlexibleRecord = true;
+ const permissionStub = sinon.stub(attachEl, 'hasPermission');
+ sinon.stub(attachEl, 'hasFacet').returns(true);
+ permissionStub.withArgs(doc, 'WriteProperties').returns(false);
+ permissionStub.withArgs(doc, 'UnsetRetention').returns(false);
+ expect(attachEl._isAvailable(doc)).equal(false);
+ });
+
+ test('Should return false if document is available & isFlexibleRecord property is true but hasFacet != true & has WriteProperties & UnsetRetention permission', async () => {
+ const doc = document;
+ doc.isFlexibleRecord = true;
+ const permissionStub = sinon.stub(attachEl, 'hasPermission');
+ sinon.stub(attachEl, 'hasFacet').returns(false);
+ permissionStub.withArgs(doc, 'WriteProperties').returns(true);
+ permissionStub.withArgs(doc, 'UnsetRetention').returns(true);
+ expect(attachEl._isAvailable(doc)).equal(false);
+ });
+
+ test('Should return false if document is available & isFlexibleRecord property is false but hasFacet = true & has WriteProperties & UnsetRetention permission', async () => {
+ const doc = document;
+ doc.isFlexibleRecord = false;
+ const permissionStub = sinon.stub(attachEl, 'hasPermission');
+ sinon.stub(attachEl, 'hasFacet').returns(true);
+ permissionStub.withArgs(doc, 'WriteProperties').returns(true);
+ permissionStub.withArgs(doc, 'UnsetRetention').returns(true);
+ expect(attachEl._isAvailable(doc)).equal(false);
+ });
+ });
+
+ suite('test _computeLabel', () => {
+ test('Should return label for undeclare record', async () => {
+ expect(attachEl._computeLabel()).equal('Undeclare Record');
+ });
+ });
+
+ suite('test _unretain', () => {
+ test('Should dispatch notify and document-updated event', async () => {
+ sinon.stub(attachEl.$.unretainOp, 'execute').resolves();
+ sinon.spy(attachEl, 'dispatchEvent');
+ attachEl._unretain();
+ setTimeout(() => {
+ expect(attachEl.dispatchEvent.calledTwice).to.equal(true);
+ }, 0);
+ });
+ });
+});