snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
383
apps/dotcom/client/e2e/tests/smoke/sharing.spec.ts
Normal file
383
apps/dotcom/client/e2e/tests/smoke/sharing.spec.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { getRandomName, openNewTab } from '../../fixtures/helpers'
|
||||
import { expect, test } from '../../fixtures/tla-test'
|
||||
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
|
||||
})
|
||||
|
||||
/* --------------------- Sharing -------------------- */
|
||||
|
||||
test.describe('signed in user on own file', () => {
|
||||
test('default share state', async ({ shareMenu }) => {
|
||||
await shareMenu.open()
|
||||
expect(await shareMenu.isToggleChecked()).toBe(true)
|
||||
expect(await shareMenu.getShareType()).toBe('Editor')
|
||||
})
|
||||
|
||||
test('tabs work correctly', async ({ shareMenu }) => {
|
||||
await shareMenu.open()
|
||||
expect(await shareMenu.publishTabButton.isVisible()).toBe(true)
|
||||
expect(await shareMenu.inviteTabButton.isVisible()).toBe(true)
|
||||
expect(await shareMenu.exportTabButton.isVisible()).toBe(true)
|
||||
expect(await shareMenu.anonShareTabButton.isVisible()).toBe(false)
|
||||
|
||||
// Starts on the invite tab
|
||||
expect(await shareMenu.inviteTabPage.isVisible()).toBe(true)
|
||||
expect(await shareMenu.exportTabPage.isVisible()).toBe(false)
|
||||
expect(await shareMenu.publishTabPage.isVisible()).toBe(false)
|
||||
expect(await shareMenu.anonShareTabPage.isVisible()).toBe(false)
|
||||
|
||||
// Can switch between tabs (export)
|
||||
await shareMenu.exportTabButton.click()
|
||||
expect(await shareMenu.inviteTabPage.isVisible()).toBe(false)
|
||||
expect(await shareMenu.exportTabPage.isVisible()).toBe(true)
|
||||
expect(await shareMenu.publishTabPage.isVisible()).toBe(false)
|
||||
|
||||
// Can switch between tabs (publish)
|
||||
await shareMenu.publishTabButton.click()
|
||||
expect(await shareMenu.inviteTabPage.isVisible()).toBe(false)
|
||||
expect(await shareMenu.exportTabPage.isVisible()).toBe(false)
|
||||
expect(await shareMenu.publishTabPage.isVisible()).toBe(true)
|
||||
})
|
||||
|
||||
test('a select inside the menu closes without closing the menu', async ({ page, shareMenu }) => {
|
||||
await shareMenu.open()
|
||||
await shareMenu.exportTabButton.click()
|
||||
await expect(shareMenu.exportTabPage).toBeVisible()
|
||||
|
||||
const themeSelect = page.locator('#tla-export-theme-select')
|
||||
|
||||
// Selecting an option closes the select but leaves the share menu open.
|
||||
await themeSelect.click()
|
||||
await expect(page.getByRole('listbox')).toBeVisible()
|
||||
await page.getByRole('option').last().click()
|
||||
await expect(page.getByRole('listbox')).toHaveCount(0)
|
||||
await expect(shareMenu.exportTabPage).toBeVisible()
|
||||
|
||||
// Re-clicking the open select's trigger also closes only the select. The trigger
|
||||
// is non-interactive while the select is open (Radix disables outside pointer
|
||||
// events), so click its position directly rather than via the locator.
|
||||
await themeSelect.click()
|
||||
await expect(page.getByRole('listbox')).toBeVisible()
|
||||
const box = await themeSelect.boundingBox()
|
||||
if (!box) throw new Error('theme select not found')
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2)
|
||||
await expect(page.getByRole('listbox')).toHaveCount(0)
|
||||
await expect(shareMenu.exportTabPage).toBeVisible()
|
||||
})
|
||||
|
||||
test('can unshare and reshare', async ({ page, browser, shareMenu }) => {
|
||||
// Copy the link to the current file
|
||||
await shareMenu.open()
|
||||
expect(await shareMenu.isInviteButtonVisible()).toBe(true)
|
||||
const url = await shareMenu.copyLink()
|
||||
|
||||
// Open link in an incognito window
|
||||
const { newPage, newShareMenu, newContext } = await openNewTab(browser, {
|
||||
url,
|
||||
allowClipboard: true,
|
||||
userProps: undefined,
|
||||
})
|
||||
|
||||
// The second page should have the share button and not the error
|
||||
await expect(newShareMenu.shareButton).toBeVisible()
|
||||
await expect(newPage.getByTestId('tla-error')).not.toBeVisible()
|
||||
|
||||
// Now unshare it it...
|
||||
await page.getByTestId('shared-link-shared-switch').click()
|
||||
expect(await shareMenu.isToggleChecked()).toBe(false)
|
||||
|
||||
// Reload the second page
|
||||
newPage.reload()
|
||||
|
||||
// The second page should have an error and not the share button
|
||||
await expect(newShareMenu.shareButton).not.toBeVisible()
|
||||
await expect(newPage.getByTestId('tla-error')).toBeVisible()
|
||||
|
||||
// Now reshare it...
|
||||
await page.getByTestId('shared-link-shared-switch').click()
|
||||
expect(await shareMenu.isToggleChecked()).toBe(true)
|
||||
|
||||
// Reload the second page again
|
||||
newPage.reload()
|
||||
|
||||
// The second page should have the share button and not the error again
|
||||
await expect(newShareMenu.shareButton).toBeVisible()
|
||||
await expect(newPage.getByTestId('tla-error')).not.toBeVisible()
|
||||
|
||||
await newContext.close()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('signed in user on someone elses file', () => {
|
||||
test('tabs work correctly', async ({ browser, sidebar, shareMenu }) => {
|
||||
const newName = getRandomName()
|
||||
await sidebar.renameFile(0, newName)
|
||||
await shareMenu.open()
|
||||
const url = await shareMenu.copyLink()
|
||||
|
||||
const parallelIndex = test.info().parallelIndex
|
||||
// Open link in an incognito window
|
||||
const { newShareMenu, newPage } = await openNewTab(browser, {
|
||||
url,
|
||||
allowClipboard: true,
|
||||
userProps: { user: 'suppy', index: parallelIndex },
|
||||
})
|
||||
|
||||
await expect(newPage.getByTestId('tla-sidebar').getByText(newName)).toBeVisible()
|
||||
|
||||
await newShareMenu.open()
|
||||
expect(await newShareMenu.publishTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.inviteTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabButton.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.anonShareTabButton.isVisible()).toBe(true)
|
||||
|
||||
// Starts on the anon share tab
|
||||
expect(await newShareMenu.inviteTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.publishTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(true)
|
||||
|
||||
// Can switch between tabs (export)
|
||||
await newShareMenu.exportTabButton.click()
|
||||
expect(await newShareMenu.inviteTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.publishTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('signed in user on published file', () => {
|
||||
test('tabs work correctly', async ({ shareMenu, browser }) => {
|
||||
// Publish the user's current file
|
||||
await shareMenu.open()
|
||||
expect(await shareMenu.isInviteButtonVisible()).toBe(true)
|
||||
await shareMenu.publishFile()
|
||||
const url = await shareMenu.copyLink()
|
||||
|
||||
// Open published file link in other user window
|
||||
const { newShareMenu, newContext } = await openNewTab(browser, {
|
||||
url,
|
||||
allowClipboard: true,
|
||||
userProps: { user: 'huppy', index: test.info().parallelIndex },
|
||||
})
|
||||
|
||||
await expect(newShareMenu.shareButton).toBeVisible()
|
||||
await newShareMenu.open()
|
||||
|
||||
expect(await newShareMenu.publishTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.inviteTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabButton.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.anonShareTabButton.isVisible()).toBe(true)
|
||||
|
||||
expect(await newShareMenu.publishTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.inviteTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(true) // show anon share tab page
|
||||
|
||||
// can copy the url
|
||||
const copiedUrl = await newShareMenu.copyLink()
|
||||
expect(new URL(copiedUrl).pathname).toBe(new URL(url).pathname)
|
||||
|
||||
// Switch to the export share tab
|
||||
await newShareMenu.exportTabButton.click()
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(false)
|
||||
|
||||
// Does not see the sidebar or the sidebar button
|
||||
expect(await newShareMenu.page.getByTestId('tla-sidebar').count()).toBe(0)
|
||||
expect(await newShareMenu.page.getByTestId('tla-sidebar-toggle').count()).toBe(0)
|
||||
|
||||
// Main user does see sidebar
|
||||
expect(await shareMenu.page.getByTestId('tla-sidebar').count()).toBe(1)
|
||||
expect(await shareMenu.page.getByTestId('tla-sidebar-toggle').count()).toBe(1)
|
||||
|
||||
await newContext.close()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('logged out user on scratch file', () => {
|
||||
test('tabs work correctly', async ({ browser }) => {
|
||||
const { newShareMenu, newContext } = await openNewTab(browser, {
|
||||
allowClipboard: true,
|
||||
userProps: undefined,
|
||||
})
|
||||
|
||||
// In the incognito window, we are on the scratchpad
|
||||
await expect(newShareMenu.shareButton).toBeVisible()
|
||||
await newShareMenu.open()
|
||||
expect(await newShareMenu.inviteTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabButton.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.publishTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.anonShareTabButton.isVisible()).toBe(false) // not shown on scratch
|
||||
|
||||
await newContext.close()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('logged out user on guest file', () => {
|
||||
test('tabs work correctly', async ({ shareMenu, browser }) => {
|
||||
// Share the signed in user's current file
|
||||
await shareMenu.open()
|
||||
expect(await shareMenu.isInviteButtonVisible()).toBe(true)
|
||||
await shareMenu.shareFile()
|
||||
const url = await shareMenu.copyLink()
|
||||
|
||||
// Open file link in an incognito window
|
||||
const { newShareMenu, newContext } = await openNewTab(browser, {
|
||||
url,
|
||||
allowClipboard: true,
|
||||
userProps: undefined,
|
||||
})
|
||||
|
||||
await expect(newShareMenu.shareButton).toBeVisible()
|
||||
await newShareMenu.open()
|
||||
|
||||
expect(await newShareMenu.publishTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.inviteTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabButton.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.anonShareTabButton.isVisible()).toBe(true)
|
||||
|
||||
expect(await newShareMenu.publishTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.inviteTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(true) // show anon share tab page
|
||||
|
||||
// can copy the url
|
||||
const copiedUrl = await shareMenu.copyLink()
|
||||
expect(copiedUrl).toBe(url)
|
||||
|
||||
// Switch to the export share tab
|
||||
await newShareMenu.exportTabButton.click()
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(false)
|
||||
|
||||
await newContext.close()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('logged out user on published file', () => {
|
||||
test('tabs work correctly', async ({ shareMenu, browser }) => {
|
||||
// Publish the user's current file
|
||||
await shareMenu.open()
|
||||
expect(await shareMenu.isInviteButtonVisible()).toBe(true)
|
||||
await shareMenu.publishFile()
|
||||
const url = await shareMenu.copyLink()
|
||||
|
||||
// Open published file link in an incognito window
|
||||
const { newShareMenu, newContext, newEditor } = await openNewTab(browser, {
|
||||
url,
|
||||
allowClipboard: true,
|
||||
userProps: undefined,
|
||||
})
|
||||
|
||||
await expect(newShareMenu.shareButton).toBeVisible()
|
||||
await newShareMenu.open()
|
||||
|
||||
expect(await newShareMenu.publishTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.inviteTabButton.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabButton.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.anonShareTabButton.isVisible()).toBe(true)
|
||||
|
||||
expect(await newShareMenu.publishTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.inviteTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(false)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(true) // show anon share tab page
|
||||
|
||||
// can copy the url
|
||||
const copiedUrl = await shareMenu.copyLink()
|
||||
expect(copiedUrl).toBe(url)
|
||||
|
||||
// Switch to the export share tab
|
||||
await newShareMenu.exportTabButton.click()
|
||||
expect(await newShareMenu.exportTabPage.isVisible()).toBe(true)
|
||||
expect(await newShareMenu.anonShareTabPage.isVisible()).toBe(false)
|
||||
|
||||
// download the file
|
||||
await newEditor.openPageMenu()
|
||||
const downloadButton = newEditor.page.getByText('Download')
|
||||
await expect(downloadButton).toBeVisible()
|
||||
|
||||
// check that the file downloaded
|
||||
const downloadPromise = newEditor.page.waitForEvent('download')
|
||||
|
||||
await downloadButton.click()
|
||||
const download = await downloadPromise
|
||||
expect(download.suggestedFilename().endsWith('.tldr')).toBe(true)
|
||||
|
||||
await newContext.close()
|
||||
})
|
||||
})
|
||||
|
||||
/* ------------------- Exporting ------------------- */
|
||||
test('can export a file as an image', async ({ page, shareMenu }) => {
|
||||
await page.getByTestId('tools.rectangle').click()
|
||||
await page.locator('.tl-background').click()
|
||||
const downloadPromise = page.waitForEvent('download')
|
||||
|
||||
await shareMenu.open()
|
||||
await shareMenu.exportFile()
|
||||
const download = await downloadPromise
|
||||
const suggestedFilename = download.suggestedFilename()
|
||||
expect(suggestedFilename.endsWith('.png')).toBe(true)
|
||||
const filePath = path.join('./test-results/', suggestedFilename)
|
||||
await download.saveAs(filePath)
|
||||
|
||||
expect(fs.existsSync(filePath)).toBeTruthy()
|
||||
const stats = fs.statSync(filePath)
|
||||
expect(stats.size).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('can follow a deep link to a never-seen file', async ({ editor, browser, shareMenu }) => {
|
||||
const text = Math.random().toString(36).substring(2, 15)
|
||||
await editor.createNewPage()
|
||||
await editor.createTextShape(text)
|
||||
|
||||
await shareMenu.open()
|
||||
expect(await shareMenu.isInviteButtonVisible()).toBe(true)
|
||||
const url = await shareMenu.copyLink()
|
||||
// close the share menu
|
||||
await shareMenu.page.keyboard.press('Escape')
|
||||
// create a new empty page
|
||||
await editor.createNewPage()
|
||||
|
||||
const { newEditor } = await openNewTab(browser, {
|
||||
url,
|
||||
allowClipboard: true,
|
||||
userProps: { user: 'suppy', index: test.info().parallelIndex },
|
||||
})
|
||||
|
||||
await newEditor.expectShapesCount(1)
|
||||
expect(newEditor.page.getByText(text).last()).toBeVisible()
|
||||
})
|
||||
|
||||
test('can follow a deep link to an already-seen file', async ({ editor, shareMenu, browser }) => {
|
||||
const text = Math.random().toString(36).substring(2, 15)
|
||||
|
||||
await shareMenu.open()
|
||||
await shareMenu.inviteTabButton.click()
|
||||
// close the share menu
|
||||
await shareMenu.page.keyboard.press('Escape')
|
||||
|
||||
const { newEditor, newShareMenu } = await openNewTab(browser, {
|
||||
url: editor.page.url(),
|
||||
allowClipboard: true,
|
||||
userProps: { user: 'suppy', index: test.info().parallelIndex },
|
||||
})
|
||||
|
||||
await newEditor.createNewPage()
|
||||
await newEditor.createTextShape(text)
|
||||
await newEditor.expectShapesCount(1)
|
||||
|
||||
await newShareMenu.open()
|
||||
await newShareMenu.anonShareTabButton.click()
|
||||
const url = await newShareMenu.copyLink()
|
||||
|
||||
await newEditor.createNewPage()
|
||||
|
||||
await editor.page.goto(url)
|
||||
await editor.expectShapesCount(1)
|
||||
await expect(editor.page.getByText(text).last()).toBeVisible()
|
||||
})
|
||||
Reference in New Issue
Block a user