Skip to content

Commit

Permalink
feat: delete and unpublish API
Browse files Browse the repository at this point in the history
  • Loading branch information
rofe committed Mar 13, 2024
1 parent a0eb254 commit 26f661b
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 19 deletions.
2 changes: 2 additions & 0 deletions src/extension/app/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export const EXTERNAL_EVENTS = {
STATUS_FETCHED: 'statusfetched',
RESOURCE_UPDATED: 'updated',
RESOURCE_PREVIEWED: 'previewed',
RESOURCE_DELETED: 'deleted',
RESOURCE_PUBLISHED: 'published',
RESOURCE_UNPUBLISHED: 'unpublished',
EVIRONMENT_SWITCHED: 'envswitched',
PLUGIN_USED: 'pluginused',
};
Expand Down
114 changes: 96 additions & 18 deletions src/extension/app/store/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,26 +498,34 @@ export class AppStore {
}

/**
* Reloads the current page. Abstracted for testing.
* Opens a new page. Abstracted for testing.
* @param {string} url The URL to open
* @param {string} [name] The window name (optional)
* @returns {Window} The window object
*/
reloadPage(newTab) {
if (newTab) {
// istanbul ignore next
window.open(window.location.href);
} else {
// istanbul ignore next
window.location.reload();
}
// istanbul ignore next 3
openPage(url, name) {
return window.open(url, name);
}

/**
* Opens a new page. Abstracted for testing.
* @param {string} url The URL to open
* @returns {Window} The window object
* Navigates to the provided URL in the current window. Abstracted for testing.
* @param {string} url The URL to load
*/
// istanbul ignore next 3
openPage(url) {
return window.open(url);
loadPage(url) {
window.location.href = url;
}

/**
* Reloads the current page. Abstracted for testing.
*/
reloadPage(newTab) {
if (newTab) {
this.openPage(window.location.href);

Check warning on line 525 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L524-L525

Added lines #L524 - L525 were not covered by tests
} else {
this.reloadPage(window.location.href);

Check warning on line 527 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L527

Added line #L527 was not covered by tests
}
}

/**
Expand Down Expand Up @@ -792,6 +800,40 @@ export class AppStore {
this.switchEnv('preview');
}

/**
* Deletes the current resource from preview and unpublishes it if published.
* @fires Sidekick#deleted
* @returns {Promise<AdminResponse>} The response object
*/
async delete() {
const { siteStore, status } = this;
const path = status.webPath;

Check warning on line 810 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L809-L810

Added lines #L809 - L810 were not covered by tests
let resp;
try {

Check warning on line 812 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L812

Added line #L812 was not covered by tests
// delete preview
resp = await fetch(

Check warning on line 814 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L814

Added line #L814 was not covered by tests
getAdminUrl(siteStore, 'preview', path),
{
method: 'DELETE',
...getAdminFetchOptions(),
},
);
// also unpublish if published
if (status.live && status.live.lastModified) {
await this.unpublish();

Check warning on line 823 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L822-L823

Added lines #L822 - L823 were not covered by tests
}
this.fireEvent(EXTERNAL_EVENTS.RESOURCE_DELETED, path);

Check warning on line 825 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L825

Added line #L825 was not covered by tests
} catch (e) {
// eslint-disable-next-line no-console
console.log('failed to delete', path, e);

Check warning on line 828 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L828

Added line #L828 was not covered by tests
}
return {

Check warning on line 830 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L830

Added line #L830 was not covered by tests
ok: (resp && resp.ok) || false,
status: (resp && resp.status) || 0,
path,
};
}

/**
* Publishes the page at the specified path if <code>config.host</code> is defined.
* @param {string} path The path of the page to publish
Expand Down Expand Up @@ -845,6 +887,39 @@ export class AppStore {
return resp;
}

/**
* Unpublishes the current page.
* @fires Sidekick#unpublished
* @returns {Promise<AdminResponse>} The response object
*/
async unpublish() {
if (!this.isContent()) {
return null;

Check warning on line 897 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L896-L897

Added lines #L896 - L897 were not covered by tests
}
const { siteStore, status } = this;
const path = status.webPath;

Check warning on line 900 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L899-L900

Added lines #L899 - L900 were not covered by tests

/**
* @type {AdminResponse}
*/
let resp;
try {

Check warning on line 906 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L906

Added line #L906 was not covered by tests
// delete live
resp = await fetch(

Check warning on line 908 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L908

Added line #L908 was not covered by tests
getAdminUrl(siteStore, 'live', path),
{
method: 'DELETE',
...getAdminFetchOptions(),
},
);
this.fireEvent(EXTERNAL_EVENTS.RESOURCE_UNPUBLISHED, path);

Check warning on line 915 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L915

Added line #L915 was not covered by tests
} catch (e) {
// eslint-disable-next-line no-console
console.error('failed to unpublish', path, e);

Check warning on line 918 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L918

Added line #L918 was not covered by tests
}
return resp;

Check warning on line 920 in src/extension/app/store/app.js

View check run for this annotation

Codecov / codecov/patch

src/extension/app/store/app.js#L920

Added line #L920 was not covered by tests
}

/**
* Switches to (or opens) a given environment.
* @param {string} targetEnv One of the following environments:
Expand Down Expand Up @@ -872,7 +947,10 @@ export class AppStore {
if (!status.webPath) {
// eslint-disable-next-line no-console
console.log('not ready yet, trying again in a second ...');
window.setTimeout(() => this.switchEnv(targetEnv, open), 1000);
window.setTimeout(
// istanbul ignore next
() => this.switchEnv(targetEnv, open), 1000,
);
return;
}
const envOrigin = targetEnv === 'dev' ? siteStore.devUrl.origin : `https://${envHost}`;
Expand All @@ -895,16 +973,16 @@ export class AppStore {

// switch or open env
if (open || this.isEditor()) {
window.open(envUrl, open
this.openPage(envUrl, open
? '' : `hlx-sk-env--${siteStore.owner}/${siteStore.repo}/${siteStore.ref}${status.webPath}`);
this.hideWait();
} else {
window.location.href = envUrl;
this.loadPage(envUrl);
}
this.fireEvent(EXTERNAL_EVENTS.EVIRONMENT_SWITCHED, {
sourceUrl: href,
targetUrl: envUrl,
});
this.hideWait();
}

/**
Expand Down
108 changes: 107 additions & 1 deletion test/app/store/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { EventBus } from '../../../src/extension/app/utils/event-bus.js';
import { EVENTS, MODALS } from '../../../src/extension/app/constants.js';
import { mockHelixEnvironment, restoreEnvironment } from '../../mocks/environment.js';
import { getAdminFetchOptions, getAdminUrl } from '../../../src/extension/app/utils/helix-admin.js';
import { defaultSharepointProfileResponse } from '../../fixtures/helix-admin.js';
import { defaultSharepointProfileResponse, defaultSharepointStatusResponse } from '../../fixtures/helix-admin.js';

// @ts-ignore
window.chrome = chromeMock;
Expand Down Expand Up @@ -394,6 +394,112 @@ describe('Test App Store', () => {
});
});

describe('switchEnv', async () => {
const mockStatus = defaultSharepointStatusResponse;
let sandbox;
let openPage;
let loadPage;
let instance;

beforeEach(() => {
sandbox = sinon.createSandbox();
sandbox.stub(window, 'fetch').resolves(new Response(JSON.stringify({
webPath: '/somepath',
})));
instance = appStore;
instance.siteStore = {
owner: 'adobe',
repo: 'aem-boilerplate',
ref: 'main',
innerHost: new URL(mockStatus.preview.url).hostname,
outerHost: new URL(mockStatus.live.url).hostname,
devUrl: new URL('https://localhost:3000'),
};

// Mock other functions
sandbox.stub(instance, 'fireEvent');

openPage = sandbox.spy();
loadPage = sandbox.spy();
sandbox.stub(instance, 'openPage').callsFake(openPage);
sandbox.stub(instance, 'loadPage').callsFake(loadPage);
});

afterEach(() => {
sandbox.restore();
});

it('switches from editor to preview', async () => {
instance.location = new URL(mockStatus.edit.url);
instance.status = mockStatus;
await instance.switchEnv('preview');
expect(openPage.calledWith(mockStatus.preview.url)).to.be.true;
});

it('switches from preview to editor', async () => {
instance.location = new URL(mockStatus.preview.url);
instance.status = mockStatus;
await instance.switchEnv('edit');
expect(loadPage.calledWith(mockStatus.edit.url)).to.be.true;
});

it('switches from live to preview', async () => {
instance.location = new URL(mockStatus.live.url);
instance.status = mockStatus;
await instance.switchEnv('preview');
expect(loadPage.calledWith(mockStatus.preview.url)).to.be.true;
});

it('switches from preview to live opening a new window', async () => {
instance.location = new URL(mockStatus.preview.url);
instance.status = mockStatus;
await instance.switchEnv('live', true);
expect(openPage.calledWith(mockStatus.live.url)).to.be.true;
});

it('switches from preview to dev', async () => {
instance.location = new URL(mockStatus.live.url);
instance.status = mockStatus;
await instance.switchEnv('dev');
const devUrl = new URL(
new URL(mockStatus.preview.url).pathname,
'https://localhost:3000',
);
expect(loadPage.calledWith(devUrl.href)).to.be.true;
});

it('switches to live instead of prod', async () => {
instance.location = new URL(mockStatus.preview.url);
instance.status = mockStatus;
await instance.switchEnv('prod');
expect(loadPage.calledWith(mockStatus.live.url)).to.be.true;
});

it('aborts on invaid target env', async () => {
instance.location = new URL(mockStatus.preview.url);
instance.status = mockStatus;
await instance.switchEnv('foo');
expect(openPage.calledOnce).to.be.false;
expect(loadPage.calledOnce).to.be.false;
});

it('aborts on status error', async () => {
instance.location = new URL(mockStatus.preview.url);
instance.status.error = 'some error occurred';
await instance.switchEnv('live');
expect(openPage.calledOnce).to.be.false;
expect(loadPage.calledOnce).to.be.false;
});

it('retries if status not ready yet', async () => {
const consoleSpy = sandbox.spy(console, 'log');
instance.location = new URL(mockStatus.preview.url);
instance.status = {};
await instance.switchEnv('live');
expect(consoleSpy.calledWith('not ready yet, trying again in a second ...')).to.be.true;
});
});

describe('update', async () => {
let sandbox;
let fakeFetch;
Expand Down

0 comments on commit 26f661b

Please sign in to comment.