Skip to content
Open
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
14 changes: 13 additions & 1 deletion blocks/browse/da-actionbar/da-actionbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default class DaActionBar extends LitElement {
loading: { attribute: false },
isFavorite: { attribute: false },
isHlx6: { attribute: false },
hidePublishConfs: { attribute: false },
_isCopying: { state: true },
_isDeleting: { state: true },
_isMoving: { state: true },
Expand All @@ -39,6 +40,7 @@ export default class DaActionBar extends LitElement {
this.items = [];
this.currentPath = '';
this.isFavorite = false;
this.hidePublishConfs = [];
this.canDelete = true;
}

Expand Down Expand Up @@ -143,6 +145,16 @@ export default class DaActionBar extends LitElement {
return this._canWrite && this.items.some((item) => item.ext && item.ext !== 'link') && !this._isCopying;
}

get _hidePublish() {
return this.hidePublishConfs.some(
(prefix) => this.items.some((item) => item.path?.startsWith(prefix)),
);
}

get _canPublish() {
return this._canAemAction && !this._hidePublish;
}

get _canRename() {
if (!this._canWrite) return false;
const isFolder = !this.items[0]?.ext;
Expand Down Expand Up @@ -232,7 +244,7 @@ export default class DaActionBar extends LitElement {
<button
@click=${this.handlePublish}
?disabled=${!!this.loading}
class="publish-button ${this._canAemAction ? '' : 'hide'} ${this.loading === 'publish' ? 'loading' : ''}">
class="publish-button ${this._canPublish ? '' : 'hide'} ${this.loading === 'publish' ? 'loading' : ''}">
${icon('publish')}
<span>Publish</span>
</button>
Expand Down
5 changes: 5 additions & 0 deletions blocks/browse/da-browse/da-browse.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ export default class DaBrowse extends LitElement {
if (row.key === 'editor.path') acc.push(row.value);
return acc;
}, []);
this.hidePublishConfs = rows.reduce((acc, row) => {
if (row.key === 'editor.hidePublish') acc.push(row.value);
return acc;
}, []);
}

if (!this.editorConfs || this.editorConfs.length === 0) return DEF_EDIT;
Expand Down Expand Up @@ -236,6 +240,7 @@ export default class DaBrowse extends LitElement {
class="da-list-type-${type}"
fullpath="${fullpath}"
editor="${this.editor}"
.hidePublishConfs=${this.hidePublishConfs}
@onpermissions=${this.handlePermissions}
@selectionchanged=${type === 'browse' && this._chatEnabled ? this._handleBrowseSelection : nothing}
select="${select ? true : nothing}"
Expand Down
2 changes: 2 additions & 0 deletions blocks/browse/da-list/da-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default class DaList extends LitElement {
listtype: { type: String },
fullpath: { type: String },
editor: { type: String },
hidePublishConfs: { attribute: false },
select: { type: Boolean },
sort: { type: Boolean },
drag: { type: Boolean },
Expand Down Expand Up @@ -1300,6 +1301,7 @@ export default class DaList extends LitElement {
@onpublish=${this.handlePublish}
@onshare=${this.handleShare}
.loading=${typeof this._aemActionState === 'string' ? this._aemActionState : null}
.hidePublishConfs=${this.hidePublishConfs}
currentPath="${this.fullpath}"
.isHlx6=${this._isHlx6 ?? false}
role="row"
Expand Down
38 changes: 38 additions & 0 deletions test/unit/blocks/browse/da-actionbar/da-actionbar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,44 @@ describe('DaActionBar', () => {
});
});

describe('_hidePublish / _canPublish', () => {
it('_hidePublish is false when hidePublishConfs is empty', () => {
const el = new DaActionBar();
el.items = [{ ext: 'html', path: '/org/site/blog/page.html' }];
expect(el._hidePublish).to.be.false;
});

it('_hidePublish is true when a selected item path matches a prefix', () => {
const el = new DaActionBar();
el.items = [{ ext: 'html', path: '/org/site/blog/page.html' }];
el.hidePublishConfs = ['/org/site/blog'];
expect(el._hidePublish).to.be.true;
});

it('_hidePublish is false when no selected item path matches any prefix', () => {
const el = new DaActionBar();
el.items = [{ ext: 'html', path: '/org/site/news/page.html' }];
el.hidePublishConfs = ['/org/site/blog'];
expect(el._hidePublish).to.be.false;
});

it('_canPublish is false when hidden, even with write permission', () => {
const el = new DaActionBar();
el.permissions = ['read', 'write'];
el.items = [{ ext: 'html', path: '/org/site/blog/page.html' }];
el.hidePublishConfs = ['/org/site/blog'];
expect(el._canPublish).to.be.false;
});

it('_canPublish is true when not hidden and _canAemAction is true', () => {
const el = new DaActionBar();
el.permissions = ['read', 'write'];
el.items = [{ ext: 'html', path: '/org/site/news/page.html' }];
el.hidePublishConfs = ['/org/site/blog'];
expect(el._canPublish).to.be.true;
});
});

describe('_canShare', () => {
it('Returns false when no items have a non-link extension', () => {
const el = new DaActionBar();
Expand Down
32 changes: 32 additions & 0 deletions test/unit/blocks/browse/da-browse/da-browse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,38 @@ describe('DaBrowse Component', () => {
const url = await daBrowseComp.getEditor(true);
expect(url).to.equal('/edit#');
});

// Scope mocks to the site-level config URL only (org-level gets an empty
// config) since fetchDaConfigs fetches org + site in parallel, and a
// catch-all mock would return the same rows for both, doubling entries.
function mockSiteConfig(fullpath, json) {
window.fetch = async (url) => {
// getNx2Api's config.get pings isHlx6 first (HLX_ADMIN/ping/{org}/{site}); answer that
// with a real Response (so its headers.get() call is safe) and defer everything else.
if (String(url).includes('/ping/')) return new Response('', { status: 200 });
if (url.includes(`/config${fullpath}/`)) return { ok: true, json: async () => json };
return { ok: true, json: async () => ({ data: [] }) };
};
}

it('collects editor.hidePublish rows into hidePublishConfs', async () => {
daBrowseComp.details = { fullpath: '/myorg-d/mysite/folder', org: 'myorg-d', site: 'mysite', owner: 'myorg-d', depth: 3 };
mockSiteConfig('/myorg-d/mysite', {
data: [
{ key: 'editor.path', value: '/myorg-d/mysite=https://da.live/form#' },
{ key: 'editor.hidePublish', value: '/myorg-d/mysite/blog' },
],
});
await daBrowseComp.getEditor(true);
expect(daBrowseComp.hidePublishConfs).to.deep.equal(['/myorg-d/mysite/blog']);
});

it('returns an empty hidePublishConfs when no editor.hidePublish rows exist', async () => {
daBrowseComp.details = { fullpath: '/myorg-f/mysite/folder', org: 'myorg-f', site: 'mysite', owner: 'myorg-f', depth: 3 };
mockSiteConfig('/myorg-f/mysite', { data: [{ key: 'editor.path', value: '/myorg-f/mysite=https://da.live/form#' }] });
await daBrowseComp.getEditor(true);
expect(daBrowseComp.hidePublishConfs).to.deep.equal([]);
});
});

describe('isRootFolder', () => {
Expand Down
7 changes: 7 additions & 0 deletions test/unit/blocks/browse/da-list/da-list-render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ describe('da-list render', () => {
const bar = el.shadowRoot.querySelector('da-actionbar');
expect(bar.getAttribute('data-visible')).to.equal('true');
});

it('Forwards hidePublishConfs to the action bar', async () => {
await fixture({ fullpath: '/o/r', hidePublishConfs: ['/o/r/blog'] });
await rerender();
const bar = el.shadowRoot.querySelector('da-actionbar');
expect(bar.hidePublishConfs).to.deep.equal(['/o/r/blog']);
});
});

describe('da-list pagination observer', () => {
Expand Down
18 changes: 18 additions & 0 deletions test/unit/blocks/edit/prose/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ const stubHlx6Ping = () => {
return () => { window.fetch = saved; };
};

// createConnection opens a real y-websocket connection to admin.da.live. Left
// unstubbed, the real socket's own close (e.g. a genuine 401 from the actual
// server) can race the test's synthetic 'connection-close' emit and double-count
// handler side effects (e.g. refreshToken calls). Stub WebSocket so no real
// socket is ever opened. Returns a restore fn.
const stubWebSocket = () => {
const saved = window.WebSocket;
window.WebSocket = function FakeWebSocket() {
this.readyState = 0;
this.close = () => {};
this.send = () => {};
};
return () => { window.WebSocket = saved; };
};

function buildFakeWsProvider({ withSynced = false } = {}) {
const listeners = new Map();
const winListeners = [];
Expand Down Expand Up @@ -81,12 +96,15 @@ function buildFakeWsProvider({ withSynced = false } = {}) {

describe('prose/index createConnection', () => {
let restoreFetch;
let restoreWebSocket;
beforeEach(() => {
restoreFetch = stubHlx6Ping();
restoreWebSocket = stubWebSocket();
window.localStorage.removeItem('nx-ims');
});
afterEach(() => {
restoreFetch();
restoreWebSocket();
// Always remove rather than restoring a prior value — if a leak entered
// this block, restoring it would propagate the leak to later test files.
window.localStorage.removeItem('nx-ims');
Expand Down
Loading