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
373 lines
17 KiB
TypeScript
373 lines
17 KiB
TypeScript
/**
|
|
* Test the HomeIntro screen for empty orgs and the special rendering of Examples & Templates
|
|
* page, both for anonymous and logged-in users.
|
|
*/
|
|
|
|
import {assert, driver, stackWrapFunc, WebElement} from 'mocha-webdriver';
|
|
import * as gu from 'test/nbrowser/gristUtils';
|
|
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
|
|
|
describe('HomeIntro', function() {
|
|
this.timeout(40000);
|
|
setupTestSuite({samples: true});
|
|
gu.withEnvironmentSnapshot({'GRIST_TEMPLATE_ORG': 'templates'});
|
|
|
|
describe("Anonymous on merged-org", function() {
|
|
it('should show welcome for anonymous user', async function() {
|
|
// Sign out
|
|
const session = await gu.session().personalSite.anon.login();
|
|
|
|
// Open doc-menu
|
|
await session.loadDocMenu('/');
|
|
|
|
// Check message specific to anon
|
|
assert.equal(await driver.find('.test-welcome-title').getText(), 'Welcome to Grist!');
|
|
assert.match(await driver.find('.test-welcome-text').getText(), /Sign up.*Visit our Help Center/);
|
|
|
|
// Check the sign-up link.
|
|
const signUp = await driver.findContent('.test-welcome-text a', 'Sign up');
|
|
assert.include(await signUp.getAttribute('href'), '/signin');
|
|
|
|
// Check that the link takes us to a Grist login page.
|
|
await signUp.click();
|
|
await gu.checkLoginPage();
|
|
await driver.navigate().back();
|
|
await gu.waitForDocMenuToLoad();
|
|
});
|
|
|
|
it('should should intro screen for anon', () => testIntroScreen({team: false}));
|
|
it('should not show Other Sites section', testOtherSitesSection);
|
|
it('should allow create/import from intro screen', testCreateImport.bind(null, false));
|
|
it('should allow collapsing examples and remember the state', testExamplesCollapsing);
|
|
it('should show examples workspace with the intro', testExamplesSection);
|
|
it('should render selected Examples workspace specially', testSelectedExamplesPage);
|
|
});
|
|
|
|
describe("Logged-in on merged-org", function() {
|
|
it('should show welcome for logged-in user', async function() {
|
|
// Sign in as a new user who has no docs.
|
|
const session = gu.session().personalSite.user('user3');
|
|
await session.login({
|
|
isFirstLogin: false,
|
|
freshAccount: true,
|
|
});
|
|
|
|
// Open doc-menu and dismiss the welcome questions popup
|
|
await session.loadDocMenu('/', 'skipWelcomeQuestions');
|
|
|
|
// Reload the doc-menu and dismiss the coaching call popup
|
|
await session.loadDocMenu('/');
|
|
await gu.dismissCardPopups();
|
|
|
|
// Check message specific to logged-in user
|
|
assert.match(await driver.find('.test-welcome-title').getText(), new RegExp(`Welcome.* ${session.name}`));
|
|
assert.match(await driver.find('.test-welcome-text').getText(), /Visit our Help Center/);
|
|
assert.notMatch(await driver.find('.test-welcome-text').getText(), /sign up/i);
|
|
});
|
|
|
|
it('should not show Other Sites section', testOtherSitesSection);
|
|
it('should show intro screen for empty org', () => testIntroScreen({team: false}));
|
|
it('should allow create/import from intro screen', testCreateImport.bind(null, true));
|
|
it('should allow collapsing examples and remember the state', testExamplesCollapsing);
|
|
it('should show examples workspace with the intro', testExamplesSection);
|
|
it('should allow copying examples', testCopyingExamples.bind(null, undefined));
|
|
it('should render selected Examples workspace specially', testSelectedExamplesPage);
|
|
it('should show empty workspace info', testEmptyWorkspace.bind(null, {buttons: true}));
|
|
});
|
|
|
|
describe("Logged-in on team site", function() {
|
|
it('should show welcome for logged-in user', async function() {
|
|
// Sign in as to a team that has no docs.
|
|
const session = await gu.session().teamSite.user('user1').login();
|
|
await session.loadDocMenu('/');
|
|
await session.resetSite();
|
|
|
|
// Open doc-menu
|
|
await session.loadDocMenu('/', 'skipWelcomeQuestions');
|
|
|
|
// Check message specific to logged-in user and an empty team site.
|
|
assert.match(await driver.find('.test-welcome-title').getText(), new RegExp(`Welcome.* ${session.orgName}`));
|
|
assert.match(await driver.find('.test-welcome-text').getText(), /Learn more.*find an expert/);
|
|
assert.notMatch(await driver.find('.test-welcome-text').getText(), /sign up/);
|
|
});
|
|
|
|
it('should not show Other Sites section', testOtherSitesSection);
|
|
it('should show intro screen for empty org', () => testIntroScreen({team: true}));
|
|
it('should allow create/import from intro screen', testCreateImport.bind(null, true));
|
|
it('should show examples workspace with the intro', testExamplesSection);
|
|
it('should allow copying examples', testCopyingExamples.bind(null, gu.session().teamSite.orgName));
|
|
it('should render selected Examples workspace specially', testSelectedExamplesPage);
|
|
it('should show empty workspace info', testEmptyWorkspace);
|
|
});
|
|
|
|
async function testOtherSitesSection() {
|
|
// Check that the Other Sites section is not shown.
|
|
assert.isFalse(await driver.find('.test-dm-other-sites-header').isPresent());
|
|
}
|
|
|
|
async function testIntroScreen(options: {team: boolean}) {
|
|
// TODO There is no longer a thumbnail + video link on an empty site, but it's a good place to
|
|
// check for the presence and functionality of the planned links that open an intro video.
|
|
|
|
// Check link to Help Center
|
|
assert.include(await driver.findContent('.test-welcome-text a', /Help Center/).getAttribute('href'),
|
|
'support.getgrist.com');
|
|
|
|
if (options.team) {
|
|
assert.equal(await driver.find('.test-intro-invite').getText(), 'Invite Team Members');
|
|
assert.equal(await driver.find('.test-topbar-manage-team').getText(), 'Manage Team');
|
|
} else {
|
|
assert.equal(await driver.find('.test-intro-invite').isPresent(), false);
|
|
assert.equal(await driver.find('.test-topbar-manage-team').isPresent(), false);
|
|
assert.equal(await driver.find('.test-intro-templates').getText(), 'Browse Templates');
|
|
assert.include(await driver.find('.test-intro-templates').getAttribute('href'), '/p/templates');
|
|
}
|
|
}
|
|
|
|
async function testCreateImport(isLoggedIn: boolean) {
|
|
await checkIntroButtons(isLoggedIn);
|
|
|
|
// Check that add-new menu has enabled Create Empty and Import Doc items.
|
|
await driver.find('.test-dm-add-new').doClick();
|
|
assert.equal(await driver.find('.test-dm-new-doc').matches('[class*=-disabled]'), false);
|
|
assert.equal(await driver.find('.test-dm-import').matches('[class*=-disabled]'), false);
|
|
|
|
// Create doc from add-new menu
|
|
await driver.find('.test-dm-new-doc').doClick();
|
|
await checkDocAndRestore(isLoggedIn, async () => {
|
|
await gu.dismissWelcomeTourIfNeeded();
|
|
assert.equal(await gu.getCell('A', 1).getText(), '');
|
|
if (!isLoggedIn) {
|
|
assert.equal(await driver.find('.test-tb-share-action').getText(), 'Save Document');
|
|
await driver.find('.test-tb-share').click();
|
|
assert.equal(await driver.find('.test-save-copy').isPresent(), true);
|
|
// There is no original of this document.
|
|
assert.equal(await driver.find('.test-open-original').isPresent(), false);
|
|
} else {
|
|
assert.equal(await driver.find('.test-tb-share-action').isPresent(), false);
|
|
}
|
|
});
|
|
|
|
// Import doc from add-new menu
|
|
await gu.docMenuImport('uploads/FileUploadData.csv');
|
|
await checkDocAndRestore(isLoggedIn, async () => assert.equal(await gu.getCell('fname', 1).getText(), 'george'));
|
|
}
|
|
|
|
// Wait for image to load (or fail), then check naturalWidth to ensure it loaded successfully.
|
|
const checkImageLoaded = stackWrapFunc(async function(img: WebElement) {
|
|
await driver.wait(() => img.getAttribute('complete'), 10000);
|
|
assert.isAbove(Number(await img.getAttribute('naturalWidth')), 0);
|
|
});
|
|
|
|
|
|
|
|
async function testExamplesCollapsing() {
|
|
assert.equal(await driver.find('.test-dm-pinned-doc-name').isDisplayed(), true);
|
|
|
|
// Collapse the templates section, check it's collapsed
|
|
await driver.find('.test-dm-all-docs-templates-header').click();
|
|
assert.equal(await driver.find('.test-dm-pinned-doc-name').isPresent(), false);
|
|
|
|
// Reload and check it's still collapsed.
|
|
await driver.navigate().refresh();
|
|
await gu.waitForDocMenuToLoad();
|
|
assert.equal(await driver.find('.test-dm-pinned-doc-name').isPresent(), false);
|
|
|
|
// Expand back, and check.
|
|
await driver.find('.test-dm-all-docs-templates-header').click();
|
|
assert.equal(await driver.find('.test-dm-pinned-doc-name').isDisplayed(), true);
|
|
|
|
// Reload and check it's still expanded.
|
|
await driver.navigate().refresh();
|
|
await gu.waitForDocMenuToLoad();
|
|
assert.equal(await driver.find('.test-dm-pinned-doc-name').isDisplayed(), true);
|
|
}
|
|
|
|
async function testExamplesSection() {
|
|
// Check rendering and functionality of the examples and templates section
|
|
|
|
// Check titles.
|
|
assert.includeMembers(await driver.findAll('.test-dm-pinned-doc-name', (el) => el.getText()),
|
|
['Lightweight CRM']);
|
|
|
|
// Check the Discover More Templates button is shown.
|
|
const discoverMoreButton = await driver.find('.test-dm-all-docs-templates-discover-more');
|
|
assert(await discoverMoreButton.isPresent());
|
|
assert.include(await discoverMoreButton.getAttribute('href'), '/p/templates');
|
|
|
|
// Check that the button takes us to the templates page, then go back.
|
|
await discoverMoreButton.click();
|
|
await gu.waitForDocMenuToLoad();
|
|
assert(gu.testCurrentUrl(/p\/templates/));
|
|
await driver.navigate().back();
|
|
await gu.waitForDocMenuToLoad();
|
|
|
|
// Check images.
|
|
const docItem = await driver.findContent('.test-dm-pinned-doc', /Lightweight CRM/);
|
|
assert.equal(await docItem.find('img').isPresent(), true);
|
|
await checkImageLoaded(docItem.find('img'));
|
|
|
|
// Both the image and the doc title link to the doc.
|
|
const imgHref = await docItem.find('img').findClosest('a').getAttribute('href');
|
|
const docHref = await docItem.find('.test-dm-pinned-doc-name').findClosest('a').getAttribute('href');
|
|
assert.match(docHref, /lightweight-crm/i);
|
|
assert.equal(imgHref, docHref);
|
|
|
|
// Open the example.
|
|
await docItem.find('.test-dm-pinned-doc-name').click();
|
|
await gu.waitForDocToLoad();
|
|
assert.match(await gu.getCell('Company', 1).getText(), /Sporer/);
|
|
assert.match(await driver.find('.test-bc-doc').value(), /Lightweight CRM/);
|
|
await driver.navigate().back();
|
|
await gu.waitForDocMenuToLoad();
|
|
}
|
|
|
|
async function testCopyingExamples(destination?: string) {
|
|
// Open the example to copy it. Note that we no longer support copying examples from doc menu.
|
|
// Make full copy of the example.
|
|
await driver.findContent('.test-dm-pinned-doc-name', /Lightweight CRM/).click();
|
|
await gu.waitForDocToLoad();
|
|
await driver.findWait('.test-tb-share-action', 500).click();
|
|
await gu.completeCopy({destName: 'LCRM Copy', destOrg: destination ?? 'Personal'});
|
|
await checkDocAndRestore(true, async () => {
|
|
assert.match(await gu.getCell('Company', 1).getText(), /Sporer/);
|
|
assert.match(await driver.find('.test-bc-doc').value(), /LCRM Copy/);
|
|
}, 2);
|
|
|
|
// Make a template copy of the example.
|
|
await driver.findContent('.test-dm-pinned-doc-name', /Lightweight CRM/).click();
|
|
await gu.waitForDocToLoad();
|
|
await driver.findWait('.test-tb-share-action', 500).click();
|
|
await driver.findWait('.test-save-as-template', 1000).click();
|
|
await gu.completeCopy({destName: 'LCRM Template Copy', destOrg: destination ?? 'Personal'});
|
|
await checkDocAndRestore(true, async () => {
|
|
// No data, because the file was copied as a template.
|
|
assert.equal(await gu.getCell(0, 1).getText(), '');
|
|
assert.match(await driver.find('.test-bc-doc').value(), /LCRM Template Copy/);
|
|
}, 2);
|
|
}
|
|
|
|
async function testSelectedExamplesPage() {
|
|
// Click Examples & Templates in left panel.
|
|
await driver.find('.test-dm-templates-page').click();
|
|
await gu.waitForDocMenuToLoad();
|
|
|
|
// Check Featured Templates are shown at the top of the page.
|
|
assert.equal(await driver.findWait('.test-dm-featured-templates-header', 500).getText(), 'Featured');
|
|
assert.includeMembers(
|
|
await driver.findAll('.test-dm-pinned-doc-list .test-dm-pinned-doc-name', (el) => el.getText()),
|
|
['Lightweight CRM']);
|
|
assert.includeMembers(
|
|
await driver.findAll('.test-dm-pinned-doc-list .test-dm-pinned-doc-desc', (el) => el.getText()),
|
|
['CRM template and example for linking data, and creating productive layouts.']
|
|
);
|
|
|
|
// External servers may have additional templates beyond the 3 above, so stop here.
|
|
if (server.isExternalServer()) { return; }
|
|
|
|
// Check the CRM and Invoice sections are shown below Featured Templates.
|
|
assert.includeMembers(
|
|
await driver.findAll('.test-dm-templates-header', (el) => el.getText()),
|
|
['CRM', 'Other']);
|
|
|
|
// Check that each section has the correct templates (title and description).
|
|
const [crmSection, otherSection] = await driver.findAll('.test-dm-templates');
|
|
assert.includeMembers(
|
|
await crmSection.findAll('.test-dm-pinned-doc-name', (el) => el.getText()),
|
|
['Lightweight CRM']);
|
|
assert.includeMembers(
|
|
await otherSection.findAll('.test-dm-pinned-doc-name', (el) => el.getText()),
|
|
['Afterschool Program', 'Investment Research']);
|
|
assert.includeMembers(
|
|
await crmSection.findAll('.test-dm-pinned-doc-desc', (el) => el.getText()),
|
|
['CRM template and example for linking data, and creating productive layouts.']);
|
|
assert.includeMembers(
|
|
await otherSection.findAll('.test-dm-pinned-doc-desc', (el) => el.getText()),
|
|
[
|
|
'Example for how to model business data, use formulas, and manage complexity.',
|
|
'Example for analyzing and visualizing with summary tables and linked charts.'
|
|
]);
|
|
|
|
const docItem = await driver.findContent('.test-dm-pinned-doc', /Lightweight CRM/);
|
|
assert.equal(await docItem.find('img').isPresent(), true);
|
|
await checkImageLoaded(docItem.find('img'));
|
|
}
|
|
});
|
|
|
|
async function testEmptyWorkspace(options = { buttons: false }) {
|
|
await gu.openWorkspace("Home");
|
|
assert.equal(await driver.findWait('.test-empty-workspace-info', 400).isDisplayed(), true);
|
|
// Create doc and check it is created.
|
|
await driver.find('.test-intro-create-doc').click();
|
|
await waitAndDismiss();
|
|
await emptyDockChecker();
|
|
// Check that we don't see empty info.
|
|
await driver.navigate().back();
|
|
await gu.waitForDocMenuToLoad();
|
|
assert.equal(await driver.find('.test-empty-workspace-info').isPresent(), false);
|
|
// Remove created document, it also checks that document is visible.
|
|
await deleteFirstDoc();
|
|
assert.equal(await driver.findWait('.test-empty-workspace-info', 400).isDisplayed(), true);
|
|
|
|
await checkImportDocButton(true);
|
|
}
|
|
|
|
// Wait for doc to load, check it, then return to home page, and remove the doc so that we
|
|
// can see the intro again.
|
|
async function checkDocAndRestore(
|
|
isLoggedIn: boolean,
|
|
docChecker: () => Promise<void>,
|
|
stepsBackToDocMenu: number = 1)
|
|
{
|
|
await waitAndDismiss();
|
|
await docChecker();
|
|
for (let i = 0; i < stepsBackToDocMenu; i++) {
|
|
await driver.navigate().back();
|
|
if (await gu.isAlertShown()) { await gu.acceptAlert(); }
|
|
}
|
|
await gu.waitForDocMenuToLoad();
|
|
// If not logged in, we create docs "unsaved" and don't see them in doc-menu.
|
|
if (isLoggedIn) {
|
|
// Freshly-created users will see a tip for the Add New button; dismiss it.
|
|
await gu.dismissBehavioralPrompts();
|
|
// Delete the first doc we find. We expect exactly one to exist.
|
|
await deleteFirstDoc();
|
|
}
|
|
assert.equal(await driver.find('.test-dm-doc').isPresent(), false);
|
|
}
|
|
|
|
async function waitAndDismiss() {
|
|
await gu.waitForDocToLoad();
|
|
await gu.dismissWelcomeTourIfNeeded();
|
|
}
|
|
|
|
async function deleteFirstDoc() {
|
|
assert.equal(await driver.find('.test-dm-doc').isPresent(), true);
|
|
await driver.find('.test-dm-doc').mouseMove().find('.test-dm-pinned-doc-options').click();
|
|
await driver.find('.test-dm-delete-doc').click();
|
|
await driver.find('.test-modal-confirm').click();
|
|
await driver.wait(async () => !(await driver.find('.test-modal-dialog').isPresent()), 3000);
|
|
}
|
|
|
|
async function checkIntroButtons(isLoggedIn: boolean) {
|
|
// Create doc from intro button
|
|
await checkCreateDocButton(isLoggedIn);
|
|
|
|
// Import doc from intro button
|
|
await checkImportDocButton(isLoggedIn);
|
|
}
|
|
|
|
async function checkImportDocButton(isLoggedIn: boolean) {
|
|
await gu.fileDialogUpload('uploads/FileUploadData.csv', () => driver.find('.test-intro-import-doc').click());
|
|
await checkDocAndRestore(isLoggedIn, async () => assert.equal(await gu.getCell('fname', 1).getText(), 'george'));
|
|
}
|
|
|
|
async function checkCreateDocButton(isLoggedIn: boolean) {
|
|
await driver.find('.test-intro-create-doc').click();
|
|
await checkDocAndRestore(isLoggedIn, emptyDockChecker);
|
|
}
|
|
|
|
async function emptyDockChecker() {
|
|
assert.equal(await gu.getCell('A', 1).getText(), '');
|
|
}
|