2023-03-22 13:48:50 +00:00
|
|
|
|
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';
|
2023-05-23 18:34:48 +00:00
|
|
|
|
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
|
|
|
|
import {EnvironmentSnapshot} from 'test/server/testUtils';
|
2023-03-22 13:48:50 +00:00
|
|
|
|
|
|
|
|
|
describe('DocTutorial', function () {
|
|
|
|
|
this.timeout(30000);
|
|
|
|
|
setupTestSuite();
|
|
|
|
|
|
2023-06-22 13:31:29 +00:00
|
|
|
|
gu.bigScreen();
|
|
|
|
|
|
2023-03-22 13:48:50 +00:00
|
|
|
|
let doc: DocCreationInfo;
|
|
|
|
|
let api: UserAPI;
|
2023-06-22 13:31:29 +00:00
|
|
|
|
let ownerSession: gu.Session;
|
|
|
|
|
let viewerSession: gu.Session;
|
2023-05-23 18:34:48 +00:00
|
|
|
|
let oldEnv: EnvironmentSnapshot;
|
2023-03-22 13:48:50 +00:00
|
|
|
|
|
2023-04-25 17:10:07 +00:00
|
|
|
|
const cleanup = setupTestSuite({team: true});
|
2023-03-22 13:48:50 +00:00
|
|
|
|
|
|
|
|
|
before(async () => {
|
2023-05-23 18:34:48 +00:00
|
|
|
|
oldEnv = new EnvironmentSnapshot();
|
|
|
|
|
process.env.GRIST_UI_FEATURES = 'tutorials';
|
|
|
|
|
await server.restart();
|
2023-06-22 13:31:29 +00:00
|
|
|
|
ownerSession = await gu.session().teamSite.user('support').login();
|
|
|
|
|
doc = await ownerSession.tempDoc(cleanup, 'DocTutorial.grist');
|
|
|
|
|
api = ownerSession.createHomeApi();
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await api.updateDoc(doc.id, {type: 'tutorial'});
|
|
|
|
|
await api.updateDocPermissions(doc.id, {users: {
|
|
|
|
|
'anon@getgrist.com': 'viewers',
|
|
|
|
|
'everyone@getgrist.com': 'viewers',
|
|
|
|
|
}});
|
|
|
|
|
});
|
|
|
|
|
|
2023-05-23 18:34:48 +00:00
|
|
|
|
after(async function () {
|
|
|
|
|
oldEnv.restore();
|
|
|
|
|
await server.restart();
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-22 13:48:50 +00:00
|
|
|
|
describe('when logged out', function () {
|
|
|
|
|
before(async () => {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
viewerSession = await gu.session().anon.login();
|
2023-03-22 13:48:50 +00:00
|
|
|
|
});
|
|
|
|
|
|
2023-03-28 16:11:40 +00:00
|
|
|
|
it('shows a tutorial card', async function() {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
await viewerSession.loadRelPath('/');
|
2023-03-28 16:11:40 +00:00
|
|
|
|
await gu.waitForDocMenuToLoad();
|
|
|
|
|
await gu.skipWelcomeQuestions();
|
|
|
|
|
|
|
|
|
|
assert.isTrue(await driver.find('.test-tutorial-card-content').isDisplayed());
|
|
|
|
|
// Can dismiss it.
|
|
|
|
|
await driver.find('.test-tutorial-card-close').click();
|
|
|
|
|
assert.isFalse((await driver.findAll('.test-tutorial-card-content')).length > 0);
|
|
|
|
|
// When dismissed, we can see link in the menu.
|
|
|
|
|
assert.isTrue(await driver.find('.test-dm-basic-tutorial').isDisplayed());
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-22 13:48:50 +00:00
|
|
|
|
it('redirects user to log in', async function() {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
await viewerSession.loadDoc(`/doc/${doc.id}`, false);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await gu.checkLoginPage();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('when logged in', function () {
|
|
|
|
|
let forkUrl: string;
|
|
|
|
|
|
|
|
|
|
before(async () => {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
ownerSession = await gu.session().teamSite.user('user1').login({showTips: true});
|
2023-03-22 13:48:50 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => gu.checkForErrors());
|
|
|
|
|
|
2023-03-28 16:11:40 +00:00
|
|
|
|
it('shows a tutorial card', async function() {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
await ownerSession.loadRelPath('/');
|
2023-03-28 16:11:40 +00:00
|
|
|
|
await gu.waitForDocMenuToLoad();
|
|
|
|
|
await gu.skipWelcomeQuestions();
|
|
|
|
|
|
|
|
|
|
// Make sure we have clean start.
|
2023-06-02 11:25:14 +00:00
|
|
|
|
await driver.executeScript('resetDismissedPopups();');
|
2023-03-28 16:11:40 +00:00
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
|
await gu.waitForDocMenuToLoad();
|
|
|
|
|
|
|
|
|
|
// Make sure we see the card.
|
|
|
|
|
assert.isTrue(await driver.find('.test-tutorial-card-content').isDisplayed());
|
|
|
|
|
|
|
|
|
|
// And can dismiss it.
|
|
|
|
|
await driver.find('.test-tutorial-card-close').click();
|
|
|
|
|
assert.isFalse((await driver.findAll('.test-tutorial-card-content')).length > 0);
|
|
|
|
|
|
|
|
|
|
// When dismissed, we can see link in the menu.
|
|
|
|
|
assert.isTrue(await driver.find('.test-dm-basic-tutorial').isDisplayed());
|
|
|
|
|
|
|
|
|
|
// Prefs are preserved after reload.
|
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
|
await gu.waitForDocMenuToLoad();
|
|
|
|
|
assert.isFalse((await driver.findAll('.test-tutorial-card-content')).length > 0);
|
|
|
|
|
assert.isTrue(await driver.find('.test-dm-basic-tutorial').isDisplayed());
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-22 13:48:50 +00:00
|
|
|
|
it('creates a fork the first time the document is opened', async function() {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
await ownerSession.loadDoc(`/doc/${doc.id}`);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await driver.wait(async () => {
|
|
|
|
|
forkUrl = await driver.getCurrentUrl();
|
|
|
|
|
return /~/.test(forkUrl);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows a popup containing slides generated from the GristDocTutorial table', async function() {
|
|
|
|
|
assert.isTrue(await driver.findWait('.test-doc-tutorial-popup', 2000).isDisplayed());
|
2023-03-23 18:22:28 +00:00
|
|
|
|
assert.equal(await driver.find('.test-floating-popup-header').getText(), 'DocTutorial');
|
2023-03-22 13:48:50 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup h1', 2000).getText(),
|
|
|
|
|
'Intro'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
'Welcome to the Grist Basics tutorial. We will cover the most important Grist '
|
|
|
|
|
+ 'concepts and features. Let’s get started.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2023-04-19 09:13:21 +00:00
|
|
|
|
it('can be moved around and minimized', async function() {
|
|
|
|
|
// Get the initial position of the popup.
|
|
|
|
|
const initialDims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
// Get the move handle.
|
|
|
|
|
const mover = await driver.find('.test-floating-popup-move-handle');
|
|
|
|
|
const moverInitial = await mover.getRect();
|
|
|
|
|
|
|
|
|
|
const move = async (pos: {x?: number, y?: number}) => driver.withActions((actions) => actions
|
|
|
|
|
.move({origin: driver.find('.test-floating-popup-move-handle')})
|
|
|
|
|
.press()
|
|
|
|
|
.move({origin: driver.find('.test-floating-popup-move-handle'), ...pos})
|
|
|
|
|
.release()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const resize = async (pos: {x?: number, y?: number}) => driver.withActions((actions) => actions
|
|
|
|
|
.move({origin: driver.find('.test-floating-popup-resize-handle')})
|
|
|
|
|
.press()
|
|
|
|
|
.move({origin: driver.find('.test-floating-popup-resize-handle'), ...pos})
|
|
|
|
|
.release()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Move it a little bit down.
|
|
|
|
|
await move({y: 100, x: -10});
|
|
|
|
|
|
|
|
|
|
// Check it is moved but not shrinked.
|
|
|
|
|
let dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.height, initialDims.height);
|
|
|
|
|
// And moving down.
|
|
|
|
|
assert.equal(dims.y, initialDims.y + 100);
|
|
|
|
|
assert.equal(dims.x, initialDims.x - 10);
|
|
|
|
|
|
|
|
|
|
// Now move it a little up and test it doesn't grow.
|
|
|
|
|
await move({y: -100});
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.height, initialDims.height);
|
|
|
|
|
assert.equal(dims.y, initialDims.y);
|
|
|
|
|
|
|
|
|
|
// Resize it in steps.
|
|
|
|
|
await resize({y: 10});
|
|
|
|
|
await resize({y: 10});
|
|
|
|
|
await resize({y: 10});
|
|
|
|
|
await resize({y: 10});
|
|
|
|
|
await resize({y: 10});
|
|
|
|
|
await resize({y: 50});
|
|
|
|
|
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.height, initialDims.height - 100);
|
|
|
|
|
assert.equal(dims.y, initialDims.y + 100);
|
|
|
|
|
|
|
|
|
|
// Resize back (in steps, to simulate user actions)
|
|
|
|
|
await resize({y: -20});
|
|
|
|
|
await resize({y: -20});
|
|
|
|
|
await resize({y: -20});
|
|
|
|
|
await resize({y: -40});
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.height, initialDims.height);
|
|
|
|
|
assert.equal(dims.y, initialDims.y);
|
|
|
|
|
|
|
|
|
|
// Now resize it beyond the maximum size and check it doesn't move.
|
|
|
|
|
await resize({y: -10});
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.height, initialDims.height);
|
|
|
|
|
assert.equal(dims.y, initialDims.y);
|
|
|
|
|
|
|
|
|
|
// Now resize it to the minimum size.
|
|
|
|
|
await resize({y: 700});
|
|
|
|
|
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.height, 300);
|
|
|
|
|
|
|
|
|
|
// Get window inner size.
|
|
|
|
|
const windowHeight: any = await driver.executeScript('return window.innerHeight');
|
|
|
|
|
const windowWidth: any = await driver.executeScript('return window.innerWidth');
|
|
|
|
|
assert.equal(dims.y + dims.height, windowHeight - 16);
|
|
|
|
|
|
|
|
|
|
// Now move it offscreen as low as possible.
|
|
|
|
|
await move({y: windowHeight - dims.y - 10});
|
|
|
|
|
|
|
|
|
|
// Make sure it is still visible.
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.y, windowHeight - 32); // 32px is a header size
|
|
|
|
|
assert.isBelow(dims.x, windowWidth - (32 * 4) + 1); // 120px is the right overflow value.
|
|
|
|
|
|
|
|
|
|
// Now move it to the right as far as possible.
|
|
|
|
|
await move({x: windowWidth - dims.x - 10});
|
|
|
|
|
|
|
|
|
|
// Make sure it is still visible.
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.y, windowHeight - 32);
|
|
|
|
|
assert.equal(dims.x, windowWidth - 32 * 4);
|
|
|
|
|
|
|
|
|
|
// Now move it to the left as far as possible.
|
|
|
|
|
await move({x: -3000});
|
|
|
|
|
|
|
|
|
|
// Make sure it is still visible.
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.isBelow(dims.x, 0);
|
|
|
|
|
assert.isAbove(dims.x + dims.width, 30);
|
|
|
|
|
|
|
|
|
|
const miniButton = driver.find(".test-floating-popup-minimize-maximize");
|
|
|
|
|
// Now move it back, but this time manually as the move handle is off screen.
|
|
|
|
|
await driver.withActions((a) => a
|
|
|
|
|
.move({origin: miniButton })
|
|
|
|
|
.press()
|
|
|
|
|
.move({origin: miniButton, x: Math.abs(dims.x) + 20})
|
|
|
|
|
.release()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Maximize it (it was minimized as we used the button to move it).
|
|
|
|
|
await driver.find(".test-floating-popup-minimize-maximize").click();
|
|
|
|
|
|
|
|
|
|
// Now move it to the top as far as possible.
|
|
|
|
|
// Move it a little right, so that we don't end up on the logo. Driver is clicking logo sometimes.
|
|
|
|
|
await move({y: -windowHeight, x: 100});
|
|
|
|
|
|
|
|
|
|
// Make sure it is still visible.
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.y, 16);
|
|
|
|
|
assert.isAbove(dims.x, 100);
|
|
|
|
|
assert.isBelow(dims.x, windowWidth);
|
|
|
|
|
|
|
|
|
|
// Move back where it was.
|
|
|
|
|
let moverNow = await driver.find('.test-floating-popup-move-handle').getRect();
|
|
|
|
|
await move({x: moverInitial.x - moverNow.x});
|
|
|
|
|
// And restore the size by double clicking the resizer.
|
|
|
|
|
await driver.withActions((a) => a.doubleClick(driver.find('.test-floating-popup-resize-handle')));
|
|
|
|
|
|
|
|
|
|
// Now test if we can't resize it offscreen.
|
|
|
|
|
await move({y: 10000});
|
|
|
|
|
await move({y: -100});
|
|
|
|
|
// Header is about 100px above the viewport.
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.isBelow(dims.y, windowHeight);
|
|
|
|
|
assert.isAbove(dims.x, windowHeight - 140);
|
|
|
|
|
|
|
|
|
|
// Now resize as far as possible.
|
|
|
|
|
await resize({y: 10});
|
|
|
|
|
await resize({y: 300});
|
|
|
|
|
|
|
|
|
|
// Make sure it is still visible.
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.isBelow(dims.y, windowHeight - 16);
|
|
|
|
|
|
|
|
|
|
// Now move back and resize.
|
|
|
|
|
moverNow = await driver.find('.test-floating-popup-move-handle').getRect();
|
|
|
|
|
await move({x: moverInitial.x - moverNow.x, y: moverInitial.y - moverNow.y});
|
|
|
|
|
await driver.withActions((a) => a.doubleClick(driver.find('.test-floating-popup-resize-handle')));
|
|
|
|
|
|
|
|
|
|
dims = await driver.find('.test-floating-popup-window').getRect();
|
|
|
|
|
assert.equal(dims.height, initialDims.height);
|
|
|
|
|
assert.equal(dims.y, initialDims.y);
|
|
|
|
|
assert.equal(dims.x, initialDims.x);
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-22 13:48:50 +00:00
|
|
|
|
it('is visible on all pages', async function() {
|
|
|
|
|
for (const page of ['access-rules', 'raw', 'code', 'settings']) {
|
|
|
|
|
await driver.find(`.test-tools-${page}`).click();
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup').isDisplayed());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2023-04-18 02:23:12 +00:00
|
|
|
|
it('does not break navigation via browser history', async function() {
|
|
|
|
|
// Navigating via browser history was partially broken at one point; if you
|
|
|
|
|
// started a tutorial, and wanted to leave via the browser's back button, you
|
|
|
|
|
// couldn't.
|
|
|
|
|
for (const page of ['code', 'data', 'acl']) {
|
|
|
|
|
await driver.navigate().back();
|
|
|
|
|
const currentUrl = await driver.getCurrentUrl();
|
|
|
|
|
assert.match(currentUrl, new RegExp(`/p/${page}$`));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await driver.navigate().back();
|
|
|
|
|
await driver.navigate().back();
|
|
|
|
|
await driver.findWait('.test-dm-doclist', 2000);
|
|
|
|
|
|
|
|
|
|
await driver.navigate().forward();
|
|
|
|
|
await gu.waitForDocToLoad();
|
|
|
|
|
assert.isTrue(await driver.findWait('.test-doc-tutorial-popup', 2000).isDisplayed());
|
|
|
|
|
});
|
|
|
|
|
|
2023-06-22 13:31:29 +00:00
|
|
|
|
it('shows the GristDocTutorial page and table to editors', async function() {
|
|
|
|
|
assert.deepEqual(await gu.getPageNames(), ['Page 1', 'Page 2', 'GristDocTutorial']);
|
|
|
|
|
await driver.find('.test-tools-raw').click();
|
|
|
|
|
await driver.findWait('.test-raw-data-list', 1000);
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
assert.isTrue(await driver.findContent('.test-raw-data-table-id',
|
|
|
|
|
/GristDocTutorial/).isPresent());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not show the GristDocTutorial page or table to non-editors', async function() {
|
|
|
|
|
viewerSession = await gu.session().teamSite.user('user2').login();
|
|
|
|
|
await viewerSession.loadDoc(`/doc/${doc.id}`);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
assert.deepEqual(await gu.getPageNames(), ['Page 1', 'Page 2']);
|
|
|
|
|
await driver.find('.test-tools-raw').click();
|
|
|
|
|
await driver.findWait('.test-raw-data-list', 1000);
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
assert.isFalse(await driver.findContent('.test-raw-data-table-id',
|
|
|
|
|
/GristDocTutorial/).isPresent());
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-29 19:52:04 +00:00
|
|
|
|
it('does not show behavioral tips', async function() {
|
|
|
|
|
await gu.openPage('Page 1');
|
|
|
|
|
await gu.openAddWidgetToPage();
|
|
|
|
|
assert.equal(await driver.find('.test-behavioral-prompt').isPresent(), false);
|
|
|
|
|
await gu.sendKeys(Key.ESCAPE);
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-22 13:48:50 +00:00
|
|
|
|
it('only allows users access to their own forks', async function() {
|
|
|
|
|
await driver.navigate().to(forkUrl);
|
|
|
|
|
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
2023-06-22 13:31:29 +00:00
|
|
|
|
await viewerSession.loadDoc(`/doc/${doc.id}`);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
let otherForkUrl: string;
|
|
|
|
|
await driver.wait(async () => {
|
|
|
|
|
otherForkUrl = await driver.getCurrentUrl();
|
|
|
|
|
return /~/.test(forkUrl);
|
|
|
|
|
});
|
2023-06-22 13:31:29 +00:00
|
|
|
|
ownerSession = await gu.session().teamSite.user('user1').login();
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await driver.navigate().to(otherForkUrl!);
|
|
|
|
|
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
|
|
|
|
await driver.navigate().to(forkUrl);
|
|
|
|
|
await gu.waitForDocToLoad();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('supports navigating to the next or previous slide', async function() {
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup', 2000);
|
|
|
|
|
assert.isTrue(await driver.findWait('.test-doc-tutorial-popup-next', 2000).isDisplayed());
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup-previous').isDisplayed());
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup-next').click();
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup h1').getText(),
|
|
|
|
|
'Pages'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup h1 + p').getText(),
|
|
|
|
|
'On the left-side panel is a list of pages which are views of your data. Right'
|
|
|
|
|
+ ' now, there are two pages, Page 1 and Page 2. You are looking at Page 1.'
|
|
|
|
|
);
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-next').isDisplayed());
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-previous').isDisplayed());
|
|
|
|
|
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup-previous').click();
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup h1').getText(),
|
|
|
|
|
'Intro'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
'Welcome to the Grist Basics tutorial. We will cover the most important Grist '
|
|
|
|
|
+ 'concepts and features. Let’s get started.'
|
|
|
|
|
);
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-next').isDisplayed());
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup-previous').isDisplayed());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('supports navigating to a specific slide', async function() {
|
|
|
|
|
const slide3 = await driver.find('.test-doc-tutorial-popup-slide-3');
|
2023-04-20 13:07:45 +00:00
|
|
|
|
await slide3.mouseMove();
|
|
|
|
|
await gu.waitToPass(
|
|
|
|
|
async () => assert.isTrue(await driver.find('.test-tooltip').isDisplayed()),
|
|
|
|
|
500
|
|
|
|
|
);
|
|
|
|
|
assert.equal(await driver.find('.test-tooltip').getText(), 'Adding Columns and Rows');
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await slide3.click();
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup-slide-3').click();
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup h1').getText(),
|
|
|
|
|
'Adding Columns and Rows'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
"You can add new columns to your table by clicking the ‘+’ icon"
|
|
|
|
|
+ ' to the far right of your column headers.'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const slide1 = await driver.find('.test-doc-tutorial-popup-slide-1');
|
2023-04-20 13:07:45 +00:00
|
|
|
|
await slide1.mouseMove();
|
|
|
|
|
await gu.waitToPass(
|
|
|
|
|
async () => assert.isTrue(await driver.find('.test-tooltip').isDisplayed()),
|
|
|
|
|
500
|
|
|
|
|
);
|
|
|
|
|
assert.equal(await driver.find('.test-tooltip').getText(), 'Intro');
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await slide1.click();
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup h1').getText(),
|
|
|
|
|
'Intro'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
'Welcome to the Grist Basics tutorial. We will cover the most important Grist '
|
|
|
|
|
+ 'concepts and features. Let’s get started.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('can open images in a lightbox', async function() {
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup img').click();
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-lightbox').isDisplayed());
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-lightbox-image').getAttribute('src'),
|
|
|
|
|
'https://www.getgrist.com/wp-content/uploads/2023/03/Row-1-Intro.png'
|
|
|
|
|
);
|
|
|
|
|
await driver.find('.test-doc-tutorial-lightbox-close').click();
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-lightbox').isPresent());
|
|
|
|
|
});
|
|
|
|
|
|
2023-04-20 13:07:45 +00:00
|
|
|
|
it('can be minimized and maximized by clicking a button in the header', async function() {
|
2023-03-23 18:22:28 +00:00
|
|
|
|
await driver.find('.test-floating-popup-minimize-maximize').click();
|
2023-03-22 13:48:50 +00:00
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-header').isDisplayed());
|
2023-04-20 13:07:45 +00:00
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup-body').isDisplayed());
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup-footer').isDisplayed());
|
2023-03-22 13:48:50 +00:00
|
|
|
|
|
2023-03-23 18:22:28 +00:00
|
|
|
|
await driver.find('.test-floating-popup-minimize-maximize').click();
|
2023-03-22 13:48:50 +00:00
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-header').isDisplayed());
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-body').isDisplayed());
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-footer').isDisplayed());
|
|
|
|
|
});
|
|
|
|
|
|
2023-04-20 13:07:45 +00:00
|
|
|
|
it('can be minimized and maximized by double clicking the header', async function() {
|
|
|
|
|
await driver.withActions(a => a.doubleClick(driver.find('.test-floating-popup-title')));
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-header').isDisplayed());
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup-body').isDisplayed());
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup-footer').isDisplayed());
|
|
|
|
|
|
|
|
|
|
await driver.withActions(a => a.doubleClick(driver.find('.test-floating-popup-title')));
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-header').isDisplayed());
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-body').isDisplayed());
|
|
|
|
|
assert.isTrue(await driver.find('.test-doc-tutorial-popup-footer').isDisplayed());
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-29 13:45:19 +00:00
|
|
|
|
it('does not play an easter egg when opening an anchor link encoded with rr', async function() {
|
|
|
|
|
await gu.getCell({rowNum: 1, col: 0}).click();
|
|
|
|
|
const link = await gu.getAnchor();
|
2023-04-06 16:24:38 +00:00
|
|
|
|
const easterEggLink = link.replace('.r1', '.rr1');
|
2023-03-29 13:45:19 +00:00
|
|
|
|
await driver.get(easterEggLink);
|
|
|
|
|
await gu.waitForAnchor();
|
|
|
|
|
assert.isFalse(await driver.find('.test-behavioral-prompt').isPresent());
|
|
|
|
|
await gu.assertIsRickRowing(false);
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-22 13:48:50 +00:00
|
|
|
|
it('remembers the last slide the user had open', async function() {
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup-slide-3').click();
|
|
|
|
|
// There's a 1000ms debounce in place for updates to the last slide.
|
|
|
|
|
await driver.sleep(1000 + 250);
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
|
await gu.waitForDocToLoad();
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup', 2000);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup h1', 2000).getText(),
|
|
|
|
|
'Adding Columns and Rows'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
"You can add new columns to your table by clicking the ‘+’ icon"
|
|
|
|
|
+ ' to the far right of your column headers.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('always opens the same fork whenever the document is opened', async function() {
|
|
|
|
|
assert.deepEqual(await gu.getVisibleGridCells({cols: [0], rowNums: [1]}), ['Zane Rails']);
|
|
|
|
|
await gu.getCell(0, 1).click();
|
|
|
|
|
await gu.sendKeys('Redacted', Key.ENTER);
|
|
|
|
|
await gu.waitForServer();
|
2023-06-22 13:31:29 +00:00
|
|
|
|
await ownerSession.loadDoc(`/doc/${doc.id}`);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
let currentUrl: string;
|
|
|
|
|
await driver.wait(async () => {
|
|
|
|
|
currentUrl = await driver.getCurrentUrl();
|
|
|
|
|
return /~/.test(forkUrl);
|
|
|
|
|
});
|
|
|
|
|
assert.equal(currentUrl!, forkUrl);
|
|
|
|
|
assert.deepEqual(await gu.getVisibleGridCells({cols: [0], rowNums: [1]}), ['Redacted']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('skips starting or resuming a tutorial if the open mode is set to default', async function() {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
await ownerSession.loadDoc(`/doc/${doc.id}/m/default`);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
assert.deepEqual(await gu.getPageNames(), ['Page 1', 'Page 2', 'GristDocTutorial']);
|
|
|
|
|
await driver.find('.test-tools-raw').click();
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
assert.isTrue(await driver.findContentWait('.test-raw-data-table-id',
|
|
|
|
|
/GristDocTutorial/, 2000).isPresent());
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup').isPresent());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('can restart tutorials', async function() {
|
|
|
|
|
// Simulate that the tutorial has been updated since it was forked.
|
|
|
|
|
await api.updateDoc(doc.id, {name: 'DocTutorial V2'});
|
|
|
|
|
await api.applyUserActions(doc.id, [['AddTable', 'NewTable', [{id: 'A'}]]]);
|
|
|
|
|
|
2023-04-18 02:23:12 +00:00
|
|
|
|
// Load the fork of the tutorial.
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await driver.navigate().to(forkUrl);
|
|
|
|
|
await gu.waitForDocToLoad();
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup', 2000);
|
|
|
|
|
|
|
|
|
|
// Check that the new table isn't in the fork.
|
2023-06-22 13:31:29 +00:00
|
|
|
|
assert.deepEqual(await gu.getPageNames(), ['Page 1', 'Page 2', 'GristDocTutorial']);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
assert.deepEqual(await gu.getVisibleGridCells({cols: [0], rowNums: [1]}), ['Redacted']);
|
|
|
|
|
|
2023-04-18 02:23:12 +00:00
|
|
|
|
// Restart the tutorial.
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await driver.find('.test-doc-tutorial-popup-restart').click();
|
|
|
|
|
await driver.find('.test-modal-confirm').click();
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup', 2000);
|
|
|
|
|
|
|
|
|
|
// Check that progress was reset.
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup h1', 2000).getText(),
|
|
|
|
|
'Intro'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
'Welcome to the Grist Basics tutorial. We will cover the most important Grist '
|
|
|
|
|
+ 'concepts and features. Let’s get started.'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Check that edits were reset.
|
|
|
|
|
assert.deepEqual(await gu.getVisibleGridCells({cols: [0], rowNums: [1]}), ['Zane Rails']);
|
|
|
|
|
|
2023-04-18 02:23:12 +00:00
|
|
|
|
// Check that changes made to the tutorial since it was last started are included.
|
2023-03-23 18:22:28 +00:00
|
|
|
|
assert.equal(await driver.find('.test-doc-tutorial-popup-header').getText(),
|
2023-03-22 13:48:50 +00:00
|
|
|
|
'DocTutorial V2');
|
2023-06-22 13:31:29 +00:00
|
|
|
|
assert.deepEqual(await gu.getPageNames(), ['Page 1', 'Page 2', 'GristDocTutorial', 'NewTable']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('allows editors to replace original', async function() {
|
|
|
|
|
// Make an edit to one of the tutorial slides.
|
|
|
|
|
await gu.openPage('GristDocTutorial');
|
|
|
|
|
await gu.getCell(1, 1).click();
|
|
|
|
|
await gu.sendKeys(
|
|
|
|
|
'# Intro',
|
|
|
|
|
Key.chord(Key.SHIFT, Key.ENTER),
|
|
|
|
|
Key.chord(Key.SHIFT, Key.ENTER),
|
|
|
|
|
'Welcome to the Grist Basics tutorial V2.',
|
|
|
|
|
Key.ENTER,
|
|
|
|
|
);
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
|
|
|
|
|
// Check that the update is immediately reflected in the tutorial popup.
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
'Welcome to the Grist Basics tutorial V2.'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Replace the original via the Share menu.
|
|
|
|
|
await driver.find('.test-tb-share').click();
|
|
|
|
|
await driver.find('.test-replace-original').click();
|
|
|
|
|
await driver.findWait('.test-modal-confirm', 3000).click();
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
|
|
|
|
|
// Switch to another user and restart the tutorial.
|
|
|
|
|
viewerSession = await gu.session().teamSite.user('user2').login();
|
|
|
|
|
await viewerSession.loadDoc(`/doc/${doc.id}`);
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup-restart').click();
|
|
|
|
|
await driver.find('.test-modal-confirm').click();
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup', 2000);
|
|
|
|
|
|
|
|
|
|
// Check that the changes we made earlier are included.
|
|
|
|
|
assert.equal(
|
|
|
|
|
await driver.find('.test-doc-tutorial-popup p').getText(),
|
|
|
|
|
'Welcome to the Grist Basics tutorial V2.'
|
|
|
|
|
);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
});
|
|
|
|
|
|
2023-05-01 18:24:23 +00:00
|
|
|
|
it('redirects to the last visited site when finished', async function() {
|
2023-06-22 13:31:29 +00:00
|
|
|
|
const otherSiteSession = await gu.session().personalSite.user('user1').addLogin();
|
|
|
|
|
await otherSiteSession.loadDocMenu('/');
|
|
|
|
|
await ownerSession.loadDoc(`/doc/${doc.id}`);
|
2023-05-01 18:24:23 +00:00
|
|
|
|
await driver.findWait('.test-doc-tutorial-popup-slide-13', 2000).click();
|
2023-03-22 13:48:50 +00:00
|
|
|
|
await driver.find('.test-doc-tutorial-popup-next').click();
|
2023-05-01 18:24:23 +00:00
|
|
|
|
await gu.waitForDocMenuToLoad();
|
|
|
|
|
assert.match(await driver.getCurrentUrl(), /o\/docs\/$/);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('without tutorial flag set', function () {
|
|
|
|
|
before(async () => {
|
|
|
|
|
await api.updateDoc(doc.id, {type: null});
|
2023-06-22 13:31:29 +00:00
|
|
|
|
ownerSession = await gu.session().teamSite.user('user1').login();
|
|
|
|
|
await ownerSession.loadDoc(`/doc/${doc.id}`);
|
2023-03-22 13:48:50 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => gu.checkForErrors());
|
|
|
|
|
|
|
|
|
|
it('shows the GristDocTutorial page and table', async function() {
|
|
|
|
|
assert.deepEqual(await gu.getPageNames(),
|
|
|
|
|
['Page 1', 'Page 2', 'GristDocTutorial', 'NewTable']);
|
|
|
|
|
await gu.openPage('GristDocTutorial');
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
await gu.getVisibleGridCells({cols: [1, 2], rowNums: [1]}),
|
|
|
|
|
[
|
2023-06-22 13:31:29 +00:00
|
|
|
|
'# Intro\n\nWelcome to the Grist Basics tutorial V2.',
|
2023-03-22 13:48:50 +00:00
|
|
|
|
'',
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
await driver.find('.test-tools-raw').click();
|
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
assert.isTrue(await driver.findContentWait('.test-raw-data-table-id',
|
|
|
|
|
/GristDocTutorial/, 2000).isPresent());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not show the tutorial popup', async function() {
|
|
|
|
|
assert.isFalse(await driver.find('.test-doc-tutorial-popup').isPresent());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|