Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion elements/directory/nuxeo-vocabulary-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ Polymer({
overflow: hidden;
text-overflow: ellipsis;
}

paper-button[disabled] {
opacity: 0.95;
}

paper-icon-button[disabled] {
opacity: 0.4;
color: #999;
}
</style>

<nuxeo-connection id="nx"></nuxeo-connection>
Expand Down Expand Up @@ -123,7 +132,13 @@ Polymer({

<template is="dom-if" if="[[_isVocabularySelected(selectedVocabulary)]]">
<div class="top actions">
<paper-button id="addEntry" class="text" on-tap="_createEntry" aria-labelledby="addEntryLabel">
<paper-button
id="addEntry"
class="text"
on-tap="_createEntry"
disabled$="[[_isReadOnly]]"
aria-labelledby="addEntryLabel"
>
<span id="addEntryLabel">+ [[i18n('vocabularyManagement.addEntry')]]</span>
</paper-button>
</div>
Expand All @@ -144,6 +159,7 @@ Polymer({
id="edit-button-[[index]]"
icon="nuxeo:edit"
on-tap="_editEntry"
disabled$="[[_isReadOnly]]"
aria-labelledby="editButtonTooltip"
></paper-icon-button>
<nuxeo-tooltip for="edit-button-[[index]]" id="editButtonTooltip"
Expand All @@ -154,6 +170,7 @@ Polymer({
name="delete"
icon="nuxeo:delete"
on-tap="_deleteEntry"
disabled$="[[_isReadOnly]]"
aria-labelledby="deleteButtonTooltip"
></paper-icon-button>
<nuxeo-tooltip for="delete-button-[[index]]" id="deleteButtonTooltip"
Expand Down Expand Up @@ -214,6 +231,11 @@ Polymer({
type: String,
computed: '_schemaFor(selectedVocabulary)',
},
_isReadOnly: {
type: Boolean,
computed: '_computeReadOnly(selectedVocabulary, vocabularies)',
value: false,
},
},

observers: ['_refresh(selectedVocabulary)'],
Expand All @@ -222,6 +244,17 @@ Polymer({
return entries.length ? 'display: block;' : 'display: none;';
},

// Returns true when the currently selected vocabulary is declared read-only on
// the server (NXP-31054 exposes the `readOnly` flag on each directory entity).
// Falls back to false on older servers that omit the field.
_computeReadOnly(selectedVocabulary, vocabularies) {
if (!selectedVocabulary || !Array.isArray(vocabularies)) {
return false;
}
const v = vocabularies.find((d) => d && d.name === selectedVocabulary);
return !!v?.readOnly;
},

_visibleChanged() {
if (this.visible && !this.vocabularies) {
this.$.directory.get().then((response) => {
Expand Down Expand Up @@ -362,6 +395,9 @@ Polymer({
},

_deleteEntry(e) {
if (this._isReadOnly) {
return;
}
if (window.confirm(this.i18n('vocabularyManagement.confirmDelete'))) {
const { item } = e.target.parentNode;
this.$.directory.path = `/directory/${this._encodePathSegment(item.directoryName)}/${this._encodePathSegment(
Expand Down Expand Up @@ -392,6 +428,9 @@ Polymer({
},

_editEntry(e) {
if (this._isReadOnly) {
return;
}
this._new = false;
this._selectedEntry = e.target.parentNode.item;
this.$.vocabularyEditDialog.toggle();
Expand All @@ -404,6 +443,9 @@ Polymer({
},

_save() {
if (this._isReadOnly) {
return;
}
if (!this.$.layout.validate()) {
return;
}
Expand Down Expand Up @@ -463,6 +505,9 @@ Polymer({
},

_createEntry() {
if (this._isReadOnly) {
return;
}
const emptyEntry = {
'entity-type': 'directoryEntry',
directoryName: this.selectedVocabulary,
Expand Down
91 changes: 88 additions & 3 deletions test/nuxeo-vocabulary-management.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ 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, login } from '@nuxeo/testing-helpers';
import { fixture, flush, html, login } from '@nuxeo/testing-helpers';
import '../elements/directory/nuxeo-vocabulary-management.js';

suite('nuxeo-vocabulary-management', () => {
Expand All @@ -28,8 +28,9 @@ suite('nuxeo-vocabulary-management', () => {
sinon.stub(element, 'i18n').callsFake((key) => key);
// Provide vocabularies to prevent _schemaFor from crashing
element.vocabularies = [
{ name: 'coverage', schema: 'coverage', parent: '' },
{ name: 'continent', schema: 'xvocabulary', parent: 'coverage' },
{ name: 'coverage', schema: 'coverage', parent: '', readOnly: false },
{ name: 'continent', schema: 'xvocabulary', parent: 'coverage', readOnly: false },
{ name: 'country', schema: 'xvocabulary', parent: 'continent', readOnly: true },
{ name: 'nature', schema: '', parent: '' },
];
});
Expand Down Expand Up @@ -83,6 +84,90 @@ suite('nuxeo-vocabulary-management', () => {
});
});

suite('_computeReadOnly', () => {
test('should return false when no vocabulary is selected', () => {
expect(element._computeReadOnly('', element.vocabularies)).to.be.false;
expect(element._computeReadOnly(null, element.vocabularies)).to.be.false;
});

test('should return false when vocabularies is not an array', () => {
expect(element._computeReadOnly('country', null)).to.be.false;
expect(element._computeReadOnly('country', undefined)).to.be.false;
});

test('should return false for a writable vocabulary', () => {
expect(element._computeReadOnly('coverage', element.vocabularies)).to.be.false;
});

test('should return true for a readOnly vocabulary', () => {
expect(element._computeReadOnly('country', element.vocabularies)).to.be.true;
});

test('should return false when the vocabulary has no readOnly field (legacy server)', () => {
expect(element._computeReadOnly('nature', element.vocabularies)).to.be.false;
});

test('should expose _isReadOnly as a computed property reflecting the selection', async () => {
element.selectedVocabulary = 'country';
await flush();
expect(element._isReadOnly).to.be.true;
element.selectedVocabulary = 'coverage';
await flush();
expect(element._isReadOnly).to.be.false;
});
});

suite('read-only guards', () => {
setup(async () => {
element.selectedVocabulary = 'country';
await flush();
});

test('_createEntry should be a no-op for readOnly vocabularies', () => {
const dialog = { toggle: sinon.spy() };
element.$.vocabularyEditDialog = dialog;
element._createEntry();
expect(dialog.toggle).to.not.have.been.called;
expect(element._selectedEntry).to.be.undefined;
});

test('_save should be a no-op for readOnly vocabularies', () => {
const layout = { validate: sinon.spy() };
element.$.layout = layout;
element._save();
expect(layout.validate).to.not.have.been.called;
});

test('_deleteEntry should be a no-op for readOnly vocabularies', () => {
const confirmStub = sinon.stub(window, 'confirm').returns(true);
try {
element._deleteEntry({
target: { parentNode: { item: { directoryName: 'country', properties: { id: 'x' } } } },
});
expect(confirmStub).to.not.have.been.called;
} finally {
confirmStub.restore();
}
});

test('addEntry button should be disabled (not hidden) for readOnly vocabulary', async () => {
await flush();
const btn = element.shadowRoot.querySelector('#addEntry');
Comment thread
vaibhavagarwal4-lab marked this conversation as resolved.
expect(btn, 'addEntry button should be rendered').to.exist;
expect(btn.disabled).to.be.true;
expect(btn.hasAttribute('hidden')).to.be.false;
});

test('addEntry button should not be disabled for writable vocabulary', async () => {
element.selectedVocabulary = 'coverage';
await flush();
const btn = element.shadowRoot.querySelector('#addEntry');
Comment thread
vaibhavagarwal4-lab marked this conversation as resolved.
expect(btn, 'addEntry button should be rendered').to.exist;
expect(btn.disabled).to.be.false;
expect(btn.hasAttribute('hidden')).to.be.false;
});
});

suite('_computeDialogHeading', () => {
test('should return addEntry key for new entry', () => {
expect(element._computeDialogHeading(true)).to.equal('vocabularyManagement.popup.addEntry');
Expand Down
Loading