mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
be8e13df64
Summary: Documents can now be flagged as tutorials, which causes them to display Markdown-formatted slides from a special GristDocTutorial table. Tutorial documents are forked on open, and remember the last slide a user was on. They can be restarted too, which prepares a new fork of the tutorial. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3813
311 lines
13 KiB
TypeScript
311 lines
13 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 {setupTestSuite} from 'test/nbrowser/testUtils';
|
||
|
||
describe('DocTutorial', function () {
|
||
this.timeout(30000);
|
||
setupTestSuite();
|
||
|
||
let doc: DocCreationInfo;
|
||
let api: UserAPI;
|
||
let session: gu.Session;
|
||
|
||
const cleanup = setupTestSuite();
|
||
|
||
before(async () => {
|
||
session = await gu.session().teamSite.user('support').login();
|
||
doc = await session.tempDoc(cleanup, 'DocTutorial.grist');
|
||
api = session.createHomeApi();
|
||
await api.updateDoc(doc.id, {type: 'tutorial'});
|
||
await api.updateDocPermissions(doc.id, {users: {
|
||
'anon@getgrist.com': 'viewers',
|
||
'everyone@getgrist.com': 'viewers',
|
||
}});
|
||
});
|
||
|
||
describe('when logged out', function () {
|
||
before(async () => {
|
||
session = await gu.session().anon.login();
|
||
});
|
||
|
||
it('redirects user to log in', async function() {
|
||
await session.loadDoc(`/doc/${doc.id}`, false);
|
||
await gu.checkLoginPage();
|
||
});
|
||
});
|
||
|
||
describe('when logged in', function () {
|
||
let forkUrl: string;
|
||
|
||
before(async () => {
|
||
session = await gu.session().teamSite.user('user1').login();
|
||
});
|
||
|
||
afterEach(() => gu.checkForErrors());
|
||
|
||
it('creates a fork the first time the document is opened', async function() {
|
||
await session.loadDoc(`/doc/${doc.id}`);
|
||
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());
|
||
assert.equal(await driver.find('.test-doc-tutorial-popup-title').getText(), 'DocTutorial');
|
||
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.'
|
||
);
|
||
});
|
||
|
||
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());
|
||
}
|
||
});
|
||
|
||
it('does not show the GristDocTutorial page or table', async function() {
|
||
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());
|
||
});
|
||
|
||
it('only allows users access to their own forks', async function() {
|
||
const otherSession = await gu.session().teamSite.user('user2').login();
|
||
await driver.navigate().to(forkUrl);
|
||
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
||
await otherSession.loadDoc(`/doc/${doc.id}`);
|
||
let otherForkUrl: string;
|
||
await driver.wait(async () => {
|
||
otherForkUrl = await driver.getCurrentUrl();
|
||
return /~/.test(forkUrl);
|
||
});
|
||
session = await gu.session().teamSite.user('user1').login();
|
||
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');
|
||
assert.equal(await slide3.getAttribute('title'), 'Adding Columns and Rows');
|
||
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');
|
||
assert.equal(await slide1.getAttribute('title'), 'Intro');
|
||
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());
|
||
});
|
||
|
||
it('can be minimized and maximized', async function() {
|
||
await driver.find('.test-doc-tutorial-popup-minimize-maximize').click();
|
||
assert.isTrue(await driver.find('.test-doc-tutorial-popup-header').isDisplayed());
|
||
assert.isFalse(await driver.find('.test-doc-tutorial-popup-body').isPresent());
|
||
assert.isFalse(await driver.find('.test-doc-tutorial-popup-footer').isPresent());
|
||
|
||
await driver.find('.test-doc-tutorial-popup-minimize-maximize').click();
|
||
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());
|
||
});
|
||
|
||
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();
|
||
await session.loadDoc(`/doc/${doc.id}`);
|
||
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() {
|
||
await session.loadDoc(`/doc/${doc.id}/m/default`);
|
||
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'}]]]);
|
||
|
||
// Load the current fork of the tutorial.
|
||
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.
|
||
assert.deepEqual(await gu.getPageNames(), ['Page 1', 'Page 2']);
|
||
assert.deepEqual(await gu.getVisibleGridCells({cols: [0], rowNums: [1]}), ['Redacted']);
|
||
|
||
// Restart the tutorial and wait for a new fork to be created.
|
||
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']);
|
||
|
||
// Check that changes made to the tutorial since the last fork are included.
|
||
assert.equal(await driver.find('.test-doc-tutorial-popup-title').getText(),
|
||
'DocTutorial V2');
|
||
assert.deepEqual(await gu.getPageNames(), ['Page 1', 'Page 2', 'NewTable']);
|
||
});
|
||
|
||
it('redirects to the doc menu when finished', async function() {
|
||
await driver.find('.test-doc-tutorial-popup-slide-13').click();
|
||
await driver.find('.test-doc-tutorial-popup-next').click();
|
||
await driver.findWait('.test-dm-doclist', 2000);
|
||
});
|
||
});
|
||
|
||
describe('without tutorial flag set', function () {
|
||
before(async () => {
|
||
await api.updateDoc(doc.id, {type: null});
|
||
session = await gu.session().teamSite.user('user1').login();
|
||
await session.loadDoc(`/doc/${doc.id}`);
|
||
});
|
||
|
||
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]}),
|
||
[
|
||
"# Intro\n\nWelcome to the Grist Basics tutorial. We will cover"
|
||
+ " the most important Grist concepts and features. Let’s get"
|
||
+ " started.\n\n![Grist Basics Tutorial](\n"
|
||
+ "https://www.getgrist.com/wp-content/uploads/2023/03/Row-1-Intro.png)",
|
||
'',
|
||
]
|
||
);
|
||
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());
|
||
});
|
||
});
|
||
});
|