You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/test/nbrowser/Fork.ts

625 lines
29 KiB

(core) move more tests to grist-core Summary: * Tie build and run-time docker base images to a consistent version (buster) * Extend the test login system activated by GRIST_TEST_LOGIN to ease porting tests that currently rely on cognito (many) * Make org resets work in absence of billing endpoints * When in-memory session caches are used, add missing invalidation steps * Pass org information through sign-ups/sign-ins more carefully * For CORS, explicitly trust GRIST_HOST origin when set * Move some fixtures and tests to core, focussing on tests that cover existing failures or are in the set of tests run on deployments * Retain regular `test` target to run the test suite directly, without docker * Add a `test:smoke` target to run a single simple test without `GRIST_TEST_LOGIN` activated * Add a `test:docker` target to run the tests against a grist-core docker image - since tests rely on certain fixture teams/docs, added `TEST_SUPPORT_API_KEY` and `TEST_ADD_SAMPLES` flags to ease porting The tests ported were `nbrowser` tests: `ActionLog.ts` (the first test I tend to port to anything, out of habit), `Fork.ts` (exercises a lot of doc creation paths), `HomeIntro.ts` (a lot of DocMenu exercise), and `DuplicateDocument.ts` (covers a feature known to be failing prior to this diff, the CORS tweak resolves it). Test Plan: Manually tested via `buildtools/build_core.sh`. In follow up, I want to add running the `test:docker` target in grist-core's workflows. In jenkins, only the smoke test is run. There'd be an argument for running all tests, but they include particularly slow tests, and are duplicates of tests already run (in different configuration admittedly), so I'd like to try first just using them in grist-core to gate updates to any packaged version of Grist (the docker image currently). Reviewers: alexmojaki Reviewed By: alexmojaki Subscribers: alexmojaki Differential Revision: https://phab.getgrist.com/D3176
2 years ago
import {DocCreationInfo} from 'app/common/DocListAPI';
import {UserAPI} from 'app/common/UserAPI';
import {assert, driver, Key} from 'mocha-webdriver';
import * as gu from 'test/nbrowser/gristUtils';
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
import uuidv4 from "uuid/v4";
(core) move more tests to grist-core Summary: * Tie build and run-time docker base images to a consistent version (buster) * Extend the test login system activated by GRIST_TEST_LOGIN to ease porting tests that currently rely on cognito (many) * Make org resets work in absence of billing endpoints * When in-memory session caches are used, add missing invalidation steps * Pass org information through sign-ups/sign-ins more carefully * For CORS, explicitly trust GRIST_HOST origin when set * Move some fixtures and tests to core, focussing on tests that cover existing failures or are in the set of tests run on deployments * Retain regular `test` target to run the test suite directly, without docker * Add a `test:smoke` target to run a single simple test without `GRIST_TEST_LOGIN` activated * Add a `test:docker` target to run the tests against a grist-core docker image - since tests rely on certain fixture teams/docs, added `TEST_SUPPORT_API_KEY` and `TEST_ADD_SAMPLES` flags to ease porting The tests ported were `nbrowser` tests: `ActionLog.ts` (the first test I tend to port to anything, out of habit), `Fork.ts` (exercises a lot of doc creation paths), `HomeIntro.ts` (a lot of DocMenu exercise), and `DuplicateDocument.ts` (covers a feature known to be failing prior to this diff, the CORS tweak resolves it). Test Plan: Manually tested via `buildtools/build_core.sh`. In follow up, I want to add running the `test:docker` target in grist-core's workflows. In jenkins, only the smoke test is run. There'd be an argument for running all tests, but they include particularly slow tests, and are duplicates of tests already run (in different configuration admittedly), so I'd like to try first just using them in grist-core to gate updates to any packaged version of Grist (the docker image currently). Reviewers: alexmojaki Reviewed By: alexmojaki Subscribers: alexmojaki Differential Revision: https://phab.getgrist.com/D3176
2 years ago
describe("Fork", function() {
// this is a relatively slow test in staging.
this.timeout(40000);
const cleanup = setupTestSuite();
let doc: DocCreationInfo;
let api: UserAPI;
let personal: gu.Session;
let team: gu.Session;
before(async function() {
personal = gu.session().personalSite;
team = gu.session().teamSite;
});
async function makeDocIfAbsent() {
if (doc && doc.id) { return; }
doc = await team.tempDoc(cleanup, 'Hello.grist', {load: false});
}
// Run tests with both regular docId and a custom urlId in URL, to make sure
// ids are kept straight during forking.
for (const idType of ['urlId', 'docId'] as Array<'docId'|'urlId'>) {
describe(`with ${idType} in url`, function() {
before(async function() {
// Chimpy imports a document
await team.login();
await makeDocIfAbsent();
// Chimpy invites anon to view this document as a viewer, and charon as an owner
api = team.createHomeApi();
const user2 = gu.session().user('user2').email;
const user3 = gu.session().user('user3').email;
await api.updateDocPermissions(doc.id, {users: {'anon@getgrist.com': 'viewers',
[user3]: 'viewers',
[user2]: 'owners'}});
// Optionally set a urlId
if (idType === 'urlId') {
await api.updateDoc(doc.id, {urlId: 'doc-ula'});
}
});
afterEach(() => gu.checkForErrors());
for (const mode of ['anonymous', 'logged in']) {
for (const content of ['empty', 'imported']) {
it(`can create an ${content} unsaved document when ${mode}`, async function() {
let name: string;
if (mode === 'anonymous') {
name = '@Guest';
await personal.anon.login();
} else {
name = `@${personal.name}`;
await personal.login();
}
const anonApi = personal.anon.createHomeApi();
const activeApi = (mode === 'anonymous') ? anonApi : api;
const id = await (content === 'empty' ? activeApi.newUnsavedDoc() :
activeApi.importUnsavedDoc(Buffer.from('A,B\n999,2\n'),
{filename: 'foo.csv'}));
await personal.loadDoc(`/doc/${id}`);
await gu.dismissWelcomeTourIfNeeded();
// check that the tag is there
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), true);
// check that the org name area is showing the user (not @Support).
assert.equal(await driver.find('.test-dm-org').getText(), name);
if (content === 'imported') {
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '999');
} else {
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '');
}
// editing should work
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('123');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '123');
// edits should persist across reloads
await personal.loadDoc(`/doc/${id}`);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '123');
// edits should still work
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('234');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '234');
if (mode !== 'anonymous') {
// if we log out, access should now be denied
const anonSession = await personal.anon.login();
await anonSession.loadDoc(`/doc/${id}`, false);
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
// if we log in as a different user, access should be denied
const altSession = await personal.user('user2').login();
await altSession.loadDoc(`/doc/${id}`, false);
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
}
});
}
}
it('allows a document to be forked anonymously', async function() {
// Anon loads the document, tries to modify, and fails - no write access
const anonSession = await team.anon.login();
await anonSession.loadDoc(`/doc/${doc.id}`);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('123');
await gu.waitForServer();
assert.notEqual(await gu.getCell({rowNum: 1, col: 0}).value(), '123');
// check that there is no tag
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), false);
// Anon forks the document, tries to modify, and succeeds
await anonSession.loadDoc(`/doc/${doc.id}/m/fork`);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('123');
// Check that we show some indicators of what's happening to the user in notification toasts
// (but allow for possibility that things are changing fast).
await driver.findContentWait('.test-notifier-toast-message',
/(Preparing your copy)|(You are now.*your own copy)/, 2000);
await gu.waitForServer();
await driver.findContentWait('.test-notifier-toast-message', /You are now.*your own copy/, 2000);
assert.equal(await driver.findContent('.test-notifier-toast-message', /Preparing/).isPresent(), false);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '123');
await gu.getCell({rowNum: 2, col: 0}).click();
await gu.enterCell('234');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), '234');
// The url of the doc should now be that of a fork
const forkUrl = await driver.getCurrentUrl();
assert.match(forkUrl, /~/);
// check that the tag is there
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), true);
// Open the original url and make sure the change we made is not there
await anonSession.loadDoc(`/doc/${doc.id}`);
assert.notEqual(await gu.getCell({rowNum: 1, col: 0}).value(), '123');
assert.notEqual(await gu.getCell({rowNum: 2, col: 0}).value(), '234');
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), false);
// Open the fork url and make sure the change we made is persisted there
await anonSession.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '123');
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), '234');
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), true);
});
it('allows a document to be forked anonymously multiple times', async function() {
// Anon forks the document, tries to modify, and succeeds
const anonSession = await team.anon.login();
await anonSession.loadDoc(`/doc/${doc.id}/m/fork`);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('1');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1');
const fork1 = await driver.getCurrentUrl();
await anonSession.loadDoc(`/doc/${doc.id}/m/fork`);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('2');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '2');
const fork2 = await driver.getCurrentUrl();
await anonSession.loadDoc((new URL(fork1)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1');
await anonSession.loadDoc((new URL(fork2)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '2');
});
it('allows an anonymous fork to be forked', async function() {
const anonSession = await team.anon.login();
await anonSession.loadDoc(`/doc/${doc.id}/m/fork`);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('1');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1');
const fork1 = await driver.getCurrentUrl();
assert.match(fork1, /^[^~]*~[^~]*$/); // just one ~
await anonSession.loadDoc((new URL(fork1)).pathname + '/m/fork');
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('2');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '2');
const fork2 = await driver.getCurrentUrl();
assert.match(fork2, /^[^~]*~[^~]*$/); // just one ~
await anonSession.loadDoc((new URL(fork1)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1');
await anonSession.loadDoc((new URL(fork2)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '2');
});
it('shows the right page item after forking', async function() {
const anonSession = await team.anon.login();
await anonSession.loadDoc(`/doc/${doc.id}/m/fork`);
assert.match(await driver.find('.test-treeview-itemHeader.selected').getText(), /Table1/);
// Add a new page; this immediately triggers a fork, AND selects the new page in it.
await gu.addNewPage(/Table/, /New Table/);
const urlId1 = await gu.getCurrentUrlId();
assert.match(urlId1!, /~/);
assert.match(await driver.find('.test-treeview-itemHeader.selected').getText(), /Table2/);
});
for (const user of [{access: 'viewers', name: 'user3'},
{access: 'editors', name: 'user2'}]) {
it(`allows a logged in user with ${user.access} permissions to fork`, async function() {
const userSession = await gu.session().teamSite.user(user.name as any).login();
await userSession.loadDoc(`/doc/${doc.id}/m/fork`);
assert.equal(await gu.getEmail(), userSession.email);
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), false);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('123');
await gu.waitForServer();
assert.equal(await driver.findWait('.test-unsaved-tag', 4000).isPresent(), true);
await gu.getCell({rowNum: 2, col: 0}).click();
await gu.enterCell('234');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), '234');
// The url of the doc should now be that of a fork
const forkUrl = await driver.getCurrentUrl();
assert.match(forkUrl, /~/);
// Open the original url and make sure the change we made is not there
await userSession.loadDoc(`/doc/${doc.id}`);
assert.notEqual(await gu.getCell({rowNum: 1, col: 0}).value(), '123');
assert.notEqual(await gu.getCell({rowNum: 2, col: 0}).value(), '234');
// Open the fork url and make sure the change we made is persisted there
await userSession.loadDoc((new URL(forkUrl)).pathname);
assert.notEqual(await gu.getCell({rowNum: 2, col: 0}).value(), '234');
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '123');
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), '234');
// Check we still have editing rights
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('1234');
await gu.waitForServer();
await userSession.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1234');
// Check others with write access to trunk can view but not edit our
// fork
await team.login();
await team.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getEmail(), team.email);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1234');
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('12345');
await gu.waitForServer();
await team.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1234');
const anonSession = await team.anon.login();
await anonSession.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getEmail(), 'anon@getgrist.com');
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1234');
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('12345');
await gu.waitForServer();
await anonSession.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1234');
});
}
it('controls access to forks of a logged in user correctly', async function() {
await gu.removeLogin();
const doc2 = await team.tempDoc(cleanup, 'Hello.grist', {load: false});
await api.updateDocPermissions(doc2.id, {maxInheritedRole: null});
await team.login();
await team.loadDoc(`/doc/${doc2.id}/m/fork`);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('123');
await gu.waitForServer();
// The url of the doc should now be that of a fork
const forkUrl = await driver.getCurrentUrl();
assert.match(forkUrl, /~/);
// Open the original url and make sure the change we made is not there
await team.loadDoc(`/doc/${doc2.id}`);
assert.notEqual(await gu.getCell({rowNum: 1, col: 0}).value(), '123');
// Open the fork url and make sure the change we made is persisted there
await team.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '123');
// Check we still have editing rights
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('1234');
await gu.waitForServer();
await team.loadDoc((new URL(forkUrl)).pathname);
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1234');
// Check others without view access to trunk cannot see fork
await team.user('user2').login();
await driver.get(forkUrl);
assert.equal(await driver.findWait('.test-dm-logo', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-error-header').getText(), /Access denied/);
await server.removeLogin();
await driver.get(forkUrl);
assert.equal(await driver.findWait('.test-dm-logo', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-error-header').getText(), /Access denied/);
});
it('fails to create forks with inconsistent user id', async function() {
// Note: this test is also valuable for triggering an error during openDoc flow even though
// the initial page load succeeds, and checking how such an error is shown.
const forkId = `${idType}fork${uuidv4()}`;
await team.login();
const userId = await team.getUserId();
const altSession = await team.user('user2').login();
await altSession.loadDoc(`/doc/${doc.id}~${forkId}~${userId}`, false);
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-modal-dialog').getText(), /Cannot create fork/);
// Ensure the user has a way to report the problem (although including the report button in
// the modal might be better).
assert.match(await driver.find('.test-notifier-toast-wrapper').getText(),
/Cannot create fork.*Report a problem/s);
// A new doc cannot be created either (because of access
// mismatch - for forks of the doc used in these tests, user2
// would have some access to fork via acls on trunk, but for a
// new doc user2 has no access granted via the doc, or
// workspace, or org).
await altSession.loadDoc(`/doc/new~${forkId}~${userId}`, false);
assert.equal(await driver.findWait('.test-dm-logo', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-error-header').getText(), /Access denied/);
// Same, but as an anonymous user.
const anonSession = await altSession.anon.login();
await anonSession.loadDoc(`/doc/${doc.id}~${forkId}~${userId}`, false);
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-modal-dialog').getText(), /Cannot create fork/);
// A new doc cannot be created either (because of access mismatch).
await altSession.loadDoc(`/doc/new~${forkId}~${userId}`, false);
assert.equal(await driver.findWait('.test-dm-logo', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-error-header').getText(), /Access denied/);
// Now as a user who *is* allowed to create the fork.
// But doc forks cannot be casually created this way anymore, so it still doesn't work.
await team.login();
await team.loadDoc(`/doc/${doc.id}~${forkId}~${userId}`, false);
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-modal-dialog').getText(), /Cannot create fork/);
// New document can no longer be casually created this way anymore either.
await team.loadDoc(`/doc/new~${forkId}~${userId}`, false);
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
assert.match(await driver.find('.test-modal-dialog').getText(), /Cannot create fork/);
await gu.wipeToasts();
});
it("should include the unsaved tags", async function() {
await team.login();
// open a document
const trunk = await team.tempDoc(cleanup, 'World.grist');
// make a fork
await team.loadDoc(`/doc/${trunk.id}/m/fork`);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('123');
await gu.waitForServer();
const forkUrl = await driver.getCurrentUrl();
assert.match(forkUrl, /~/);
// check that there is no tag on trunk
await team.loadDoc(`/doc/${trunk.id}`);
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), false);
// open same document with the fork bit in the URL
await team.loadDoc((new URL(forkUrl)).pathname);
// check that the tag is there
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), true);
});
it('handles url history correctly', async function() {
await team.login();
await makeDocIfAbsent();
await team.loadDoc(`/doc/${doc.id}/m/fork`);
const initialUrl = await driver.getCurrentUrl();
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), false);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('2');
await gu.waitForServer();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '2');
const forkUrl = await driver.getCurrentUrl();
assert.equal(await driver.findWait('.test-unsaved-tag', 4000).isPresent(), true);
await driver.executeScript('history.back()');
await gu.waitForUrl(/\/m\/fork/);
assert.equal(await driver.getCurrentUrl(), initialUrl);
await gu.waitForDocToLoad();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), 'hello');
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), false);
await driver.executeScript('history.forward()');
await gu.waitForUrl(/~/);
assert.equal(await driver.getCurrentUrl(), forkUrl);
await gu.waitForDocToLoad();
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '2');
assert.equal(await driver.find('.test-unsaved-tag').isPresent(), true);
});
it('disables document renaming for forks', async function() {
await team.login();
await team.loadDoc(`/doc/${doc.id}/m/fork`);
assert.equal(await driver.find('.test-bc-doc').getAttribute('disabled'), null);
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('2');
await gu.waitForServer();
await gu.waitToPass(async () => {
assert.equal(await driver.find('.test-bc-doc').getAttribute('disabled'), 'true');
});
});
it('navigating browser history play well with the add new menu', async function() {
await team.login();
await makeDocIfAbsent();
await team.loadDoc(`/doc/${doc.id}/m/fork`);
const count = await getAddNewEntryCount();
// edit one cell
await gu.getCell({rowNum: 1, col: 0}).click();
await gu.enterCell('2');
await gu.waitForServer();
// check we're on a fork
await gu.waitForUrl(/~/);
// navigate back history
await driver.navigate().back();
await gu.waitForDocToLoad();
// check number of entries in add new menu are the same
assert.equal(await getAddNewEntryCount(), count);
// helper that get the number of items in the add new menu
async function getAddNewEntryCount() {
await driver.find('.test-dp-add-new').click();
const items = await driver.findAll('.grist-floating-menu li', e => e.getText());
assert.include(items, "Import from file");
await driver.sendKeys(Key.ESCAPE);
return items.length;
}
});
it('can replace a trunk document with a fork via api', async function() {
await team.login();
await makeDocIfAbsent();
await team.loadDoc(`/doc/${doc.id}/m/fork`);
// edit one cell
await gu.getCell({rowNum: 2, col: 0}).click();
const v1 = await gu.getCell({rowNum: 2, col: 0}).getText();
const v2 = `${v1}_tweaked`;
await gu.enterCell(v2);
await gu.waitForServer();
// check we're on a fork
await gu.waitForUrl(/~/);
const urlId = await gu.getCurrentUrlId();
// open trunk again, to test that page is reloaded after replacement
await team.loadDoc(`/doc/${doc.id}`);
// replace the trunk with the fork
assert.notEqual(urlId, doc.id);
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), v1);
const docApi = team.createHomeApi().getDocAPI(doc.id);
await docApi.replace({sourceDocId: urlId!});
// check that replacement worked (giving a little time for page reload)
await gu.waitToPass(async () => {
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), v2);
}, 4000);
// have a different user make a doc we don't have access to
const altSession = await personal.user('user2').login();
const altDoc = await altSession.tempDoc(cleanup, 'Hello.grist');
await gu.dismissWelcomeTourIfNeeded();
await gu.getCell({rowNum: 2, col: 0}).click();
await gu.enterCell('altDoc');
await gu.waitForServer();
// replacement should fail for document not found
// (error is "not found" for document in a different team site
// or team site vs personal site)
await assert.isRejected(docApi.replace({sourceDocId: altDoc.id}), /not found/);
// replacement should fail for document not accessible
await assert.isRejected(personal.createHomeApi().getDocAPI(doc.id).replace({sourceDocId: altDoc.id}),
/access denied/);
// check cell content does not change
await altSession.loadDoc(`/doc/${altDoc.id}`);
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), 'altDoc');
await team.login();
await team.loadDoc(`/doc/${doc.id}`);
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), v2);
});
it('can replace a trunk document with a fork via UI', async function() {
await team.login();
await makeDocIfAbsent();
await team.loadDoc(`/doc/${doc.id}/m/fork`);
// edit one cell.
await gu.getCell({rowNum: 2, col: 0}).click();
const v1 = await gu.getCell({rowNum: 2, col: 0}).getText();
const v2 = `${v1}_tweaked`;
await gu.enterCell(v2);
await gu.waitForServer();
// check we're on a fork.
await gu.waitForUrl(/~/);
const forkUrlId = await gu.getCurrentUrlId();
assert.equal(await driver.findWait('.test-unsaved-tag', 4000).isPresent(), true);
// check Replace Original gives expected button, and press it.
await driver.find('.test-tb-share').click();
await driver.find('.test-replace-original').click();
let confirmButton = driver.findWait('.test-modal-confirm', 1000);
assert.equal(await confirmButton.getText(), 'Update');
await confirmButton.click();
// check we're no longer on a fork, but still have the change made on the fork.
await gu.waitForUrl(/^[^~]*$/, 6000);
await gu.waitForDocToLoad();
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), v2);
// edit the cell again.
await gu.getCell({rowNum: 2, col: 0}).click();
const v3 = `${v2}_tweaked`;
await gu.enterCell(v3);
await gu.waitForServer();
// revisit the fork.
await team.loadDoc(`/doc/${forkUrlId}`);
assert.equal(await driver.findWait('.test-unsaved-tag', 4000).isPresent(), true);
// check Replace Original gives a scarier button, and press it anyway.
await driver.find('.test-tb-share').click();
await driver.find('.test-replace-original').click();
confirmButton = driver.findWait('.test-modal-confirm', 1000);
assert.equal(await confirmButton.getText(), 'Overwrite');
await confirmButton.click();
// check we're no longer on a fork, but have the fork's content.
await gu.waitForUrl(/^[^~]*$/, 6000);
await gu.waitForDocToLoad();
assert.equal(await gu.getCell({rowNum: 2, col: 0}).getText(), v2);
// revisit the fork.
await team.loadDoc(`/doc/${forkUrlId}`);
assert.equal(await driver.findWait('.test-unsaved-tag', 4000).isPresent(), true);
// check Replace Original mentions that the document is the same as the trunk.
await driver.find('.test-tb-share').click();
await driver.find('.test-replace-original').click();
confirmButton = driver.findWait('.test-modal-confirm', 1000);
assert.equal(await confirmButton.getText(), 'Update');
assert.match(await driver.find('.test-modal-dialog').getText(),
/already identical/);
});
it('gives an error when replacing without write access via UI', async function() {
await team.login();
await makeDocIfAbsent();
// Give view access to a friend.
const altSession = await team.user('user2').login();
await api.updateDocPermissions(doc.id, {users: {[altSession.email]: 'viewers'}});
try {
await team.loadDoc(`/doc/${doc.id}/m/fork`);
// edit one cell.
await gu.getCell({rowNum: 2, col: 0}).click();
const v1 = await gu.getCell({rowNum: 2, col: 0}).getText();
const v2 = `${v1}_tweaked`;
await gu.enterCell(v2);
await gu.waitForServer();
// check we're on a fork.
await gu.waitForUrl(/~/);
await gu.waitForDocToLoad();
assert.equal(await driver.findWait('.test-unsaved-tag', 4000).isPresent(), true);
// check Replace Original does not let us proceed because we don't have
// editing rights on trunk.
await driver.find('.test-tb-share').click();
assert.equal(await driver.find('.test-replace-original').matches('.disabled'), true);
// Clicking the disabled element does nothing.
await driver.find('.test-replace-original').click();
assert.equal(await driver.find('.grist-floating-menu').isDisplayed(), true);
await assert.isRejected(driver.findWait('.test-modal-dialog', 500), /Waiting for element/);
} finally {
await api.updateDocPermissions(doc.id, {users: {[altSession.email]: null}});
}
});
});
}
});