mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
3dadf93c98
Summary: The new "copyDoc" query parameter on the login page sets a short-lived cookie, which is then read when welcoming a new user to copy that document to their Home workspace, and redirect to it. Currently, only templates and bare forks set this parameter. A new API endpoint for copying a document to a workspace was also added. Test Plan: Browser tests. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3992
670 lines
30 KiB
TypeScript
670 lines
30 KiB
TypeScript
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";
|
|
|
|
describe("Fork", function() {
|
|
|
|
// this is a relatively slow test in staging.
|
|
this.timeout(60000);
|
|
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});
|
|
}
|
|
|
|
async function getForks(api: UserAPI) {
|
|
const wss = await api.getOrgWorkspaces('current');
|
|
const forks = wss
|
|
.find(ws => ws.name === team.workspace)
|
|
?.docs
|
|
.find(d => d.id === doc.id)
|
|
?.forks;
|
|
return forks ?? [];
|
|
}
|
|
|
|
async function testForksOfForks(isLoggedIn: boolean = true) {
|
|
const session = isLoggedIn ? await team.login() : await team.anon.login();
|
|
await session.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 forkUrl = await driver.getCurrentUrl();
|
|
assert.match(forkUrl, isLoggedIn ? /^[^~]*~[^~]+~\d+$/ : /^[^~]*~[^~]*$/);
|
|
await gu.refreshDismiss();
|
|
await session.loadDoc((new URL(forkUrl)).pathname + '/m/fork');
|
|
await gu.getCell({rowNum: 1, col: 0}).click();
|
|
await gu.enterCell('2');
|
|
await gu.waitForServer();
|
|
await driver.findContentWait(
|
|
'.test-notifier-toast-wrapper',
|
|
/Cannot fork a document that's already a fork.*Report a problem/s,
|
|
2000
|
|
);
|
|
assert.equal(await gu.getCell({rowNum: 1, col: 0}).getText(), '1');
|
|
assert.equal(await driver.getCurrentUrl(), `${forkUrl}/m/fork`);
|
|
await gu.wipeToasts();
|
|
await gu.refreshDismiss();
|
|
}
|
|
|
|
// 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 visitedSites: string[];
|
|
if (mode === 'anonymous') {
|
|
visitedSites = ['Grist Templates'];
|
|
await personal.anon.login();
|
|
} else {
|
|
visitedSites = ['Test Grist', `@${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 one of the last visited sites. this is
|
|
// an imprecise check; doing an assert.equal instead is possible, but would require
|
|
// changing this test significantly.
|
|
assert.include(visitedSites, await driver.find('.test-dm-org').getText());
|
|
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');
|
|
await gu.refreshDismiss();
|
|
// 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');
|
|
await gu.refreshDismiss();
|
|
|
|
if (mode !== 'anonymous') {
|
|
// if we log out, access should now be denied
|
|
const anonSession = await personal.anon.login();
|
|
await anonSession.loadDoc(`/doc/${id}`, {wait: 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}`, {wait: 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);
|
|
await gu.refreshDismiss();
|
|
|
|
// 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 gu.refreshDismiss();
|
|
|
|
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 gu.refreshDismiss();
|
|
|
|
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('does not allow an anonymous fork to be forked', () => testForksOfForks(false));
|
|
|
|
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/, {dismissTips: true});
|
|
const urlId1 = await gu.getCurrentUrlId();
|
|
assert.match(urlId1!, /~/);
|
|
assert.match(await driver.find('.test-treeview-itemHeader.selected').getText(), /Table2/);
|
|
await gu.refreshDismiss();
|
|
});
|
|
|
|
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, /~/);
|
|
|
|
// Check that the fork was saved to the home db
|
|
const api = userSession.createHomeApi();
|
|
const forks = await getForks(api);
|
|
const forkId = forkUrl.match(/\/[\w-]+~(\w+)(?:~\w)?/)?.[1];
|
|
const fork = forks.find(f => f.id === forkId);
|
|
assert.isDefined(fork);
|
|
assert.equal(fork!.trunkId, doc.id);
|
|
await gu.refreshDismiss();
|
|
|
|
// 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 gu.refreshDismiss();
|
|
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 gu.refreshDismiss();
|
|
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 gu.refreshDismiss();
|
|
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, /~/);
|
|
await gu.refreshDismiss();
|
|
|
|
// 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 gu.refreshDismiss();
|
|
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.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
|
assert.equal(await driver.find('.test-dm-logo').isDisplayed(), true);
|
|
|
|
await server.removeLogin();
|
|
await driver.get(forkUrl);
|
|
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
|
assert.equal(await driver.find('.test-dm-logo').isDisplayed(), true);
|
|
});
|
|
|
|
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}`, {wait: false});
|
|
|
|
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
|
|
assert.match(await driver.find('.test-modal-dialog').getText(), /Document fork not found/);
|
|
|
|
// 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(),
|
|
/Document fork not found.*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}`, {wait: false});
|
|
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
|
assert.equal(await driver.find('.test-dm-logo').isDisplayed(), true);
|
|
|
|
// Same, but as an anonymous user.
|
|
const anonSession = await altSession.anon.login();
|
|
await anonSession.loadDoc(`/doc/${doc.id}~${forkId}~${userId}`, {wait: false});
|
|
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
|
|
assert.match(await driver.find('.test-modal-dialog').getText(), /Document fork not found/);
|
|
|
|
// A new doc cannot be created either (because of access mismatch).
|
|
await altSession.loadDoc(`/doc/new~${forkId}~${userId}`, {wait: false});
|
|
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
|
assert.equal(await driver.find('.test-dm-logo').isDisplayed(), true);
|
|
|
|
// 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}`, {wait: false});
|
|
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
|
|
assert.match(await driver.find('.test-modal-dialog').getText(), /Document fork not found/);
|
|
|
|
// New document can no longer be casually created this way anymore either.
|
|
await team.loadDoc(`/doc/new~${forkId}~${userId}`, {wait: false});
|
|
assert.equal(await driver.findWait('.test-modal-dialog', 2000).isDisplayed(), true);
|
|
assert.match(await driver.find('.test-modal-dialog').getText(), /Document fork not found/);
|
|
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, /~/);
|
|
await gu.refreshDismiss();
|
|
|
|
// 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);
|
|
await gu.refreshDismiss();
|
|
});
|
|
|
|
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');
|
|
});
|
|
await gu.refreshDismiss();
|
|
});
|
|
|
|
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();
|
|
await gu.refreshDismiss();
|
|
|
|
// 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', 3000);
|
|
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', 3000);
|
|
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', 3000);
|
|
assert.equal(await confirmButton.getText(), 'Update');
|
|
assert.match(await driver.find('.test-modal-dialog').getText(),
|
|
/already identical/);
|
|
await gu.refreshDismiss();
|
|
});
|
|
|
|
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/);
|
|
await gu.refreshDismiss();
|
|
} finally {
|
|
await api.updateDocPermissions(doc.id, {users: {[altSession.email]: null}});
|
|
}
|
|
});
|
|
|
|
it('does not allow a fork to be forked', testForksOfForks);
|
|
});
|
|
}
|
|
});
|