2
0

refactor(editor): ♻️ Undo / Redo buttons + structure refacto

Yet another huge refacto... While implementing undo and redo features I understood that I updated the stored typebot too many times (i.e. on each key input) so I had to rethink it entirely. I also moved around some files.
This commit is contained in:
Baptiste Arnaud
2022-02-02 08:05:02 +01:00
parent fc1d654772
commit 8a350eee6c
153 changed files with 1512 additions and 1352 deletions

View File

@@ -6,6 +6,7 @@ import { updateUser } from '../services/database'
test.describe('Account page', () => {
test('should edit user info properly', async ({ page }) => {
await updateUser({ name: 'Default Name' })
await page.goto('/account')
const saveButton = page.locator('button:has-text("Save")')
await expect(saveButton).toBeHidden()

View File

@@ -6,6 +6,7 @@ import {
import { BubbleStepType, defaultImageBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import path from 'path'
import { generate } from 'short-uuid'
const unsplashImageSrc =
'https://images.unsplash.com/photo-1504297050568-910d24c426d3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80'
@@ -13,7 +14,7 @@ const unsplashImageSrc =
test.describe.parallel('Image bubble step', () => {
test.describe('Content settings', () => {
test('should upload image file correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-image-upload'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@@ -41,7 +42,7 @@ test.describe.parallel('Image bubble step', () => {
})
test('should import image link correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-image-link'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@@ -64,7 +65,7 @@ test.describe.parallel('Image bubble step', () => {
})
test('should import gifs correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-gifs'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@@ -89,7 +90,7 @@ test.describe.parallel('Image bubble step', () => {
test.describe('Preview', () => {
test('should display correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview'
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { BubbleStepType, defaultTextBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'text-bubble-step'
import { generate } from 'short-uuid'
test.describe('Text bubble step', () => {
test('rich text features should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -9,6 +9,7 @@ import {
VideoBubbleContentType,
} from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import { generate } from 'short-uuid'
const videoSrc =
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'
@@ -18,7 +19,7 @@ const vimeoVideoSrc = 'https://vimeo.com/649301125'
test.describe.parallel('Video bubble step', () => {
test.describe('Content settings', () => {
test('should import video url correctly', async ({ page }) => {
const typebotId = 'video-bubble-step-link'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@@ -42,7 +43,7 @@ test.describe.parallel('Video bubble step', () => {
test.describe('Preview', () => {
test('should display video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-video'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@@ -64,7 +65,7 @@ test.describe.parallel('Video bubble step', () => {
})
test('should display youtube video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-youtube'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@@ -88,7 +89,7 @@ test.describe.parallel('Video bubble step', () => {
})
test('should display vimeo video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-vimeo'
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -1,4 +1,5 @@
import test, { expect, Page } from '@playwright/test'
import { generate } from 'short-uuid'
import { createFolders, createTypebots } from '../services/database'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
@@ -20,6 +21,7 @@ test.describe('Dashboard page', () => {
await page.click('text="New folder"')
await page.fill('input', 'My folder #2')
await page.press('input', 'Enter')
await waitForNextApiCall(page)
await page.click('li:has-text("My folder #2")')
await expect(page.locator('h1 >> text="My folder #2"')).toBeVisible()
@@ -32,10 +34,7 @@ test.describe('Dashboard page', () => {
})
test('folders and typebots should be deletable', async ({ page }) => {
await createFolders([
{ id: 'folder1', name: 'Folder #1' },
{ id: 'folder2', name: 'Folder #2' },
])
await createFolders([{ name: 'Folder #1' }, { name: 'Folder #2' }])
await createTypebots([{ id: 'deletable-typebot', name: 'Typebot #1' }])
await page.goto('/typebots')
await page.click('button[aria-label="Show Folder #1 menu"]')
@@ -49,10 +48,9 @@ test.describe('Dashboard page', () => {
})
test('folders and typebots should be movable', async ({ page }) => {
await createFolders([{ id: 'droppable-folder', name: 'Droppable folder' }])
await createTypebots([
{ id: 'draggable-typebot', name: 'Draggable typebot' },
])
const droppableFolderId = generate()
await createFolders([{ id: droppableFolderId, name: 'Droppable folder' }])
await createTypebots([{ name: 'Draggable typebot' }])
await page.goto('/typebots')
const typebotButton = page.locator('li:has-text("Draggable typebot")')
const folderButton = page.locator('li:has-text("Droppable folder")')
@@ -63,7 +61,7 @@ test.describe('Dashboard page', () => {
await waitForNextApiCall(page)
await expect(typebotButton).toBeHidden()
await folderButton.click()
await expect(page).toHaveURL(/folders\/droppable-folder/)
await expect(page).toHaveURL(new RegExp(`/folders/${droppableFolderId}`))
await expect(typebotButton).toBeVisible()
await page.dragAndDrop(
'li:has-text("Draggable typebot")',

View File

@@ -0,0 +1,28 @@
import test, { expect } from '@playwright/test'
import { createTypebots, parseDefaultBlockWithStep } from '../services/database'
import { defaultTextInputOptions, InputStepType } from 'models'
import { generate } from 'short-uuid'
test.describe('Editor', () => {
test('Undo / Redo buttons should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.TEXT,
options: defaultTextInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Block #1', { button: 'right' })
await page.click('text=Delete')
await expect(page.locator('text=Block #1')).toBeHidden()
await page.click('button[aria-label="Undo"]')
await expect(page.locator('text=Block #1')).toBeVisible()
await page.click('button[aria-label="Redo"]')
await expect(page.locator('text=Block #1')).toBeHidden()
})
})

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultChoiceInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'buttons-input-step'
import { generate } from 'short-uuid'
test.describe.parallel('Buttons input step', () => {
test('can edit button items', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultDateInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'date-input-step'
import { generate } from 'short-uuid'
test.describe('Date input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultEmailInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'email-input-step'
import { generate } from 'short-uuid'
test.describe('Email input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultNumberInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'number-input-step'
import { generate } from 'short-uuid'
test.describe('Number input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultPhoneInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'phone-input-step'
import { generate } from 'short-uuid'
test.describe('Phone input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultTextInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'text-input-step'
import { generate } from 'short-uuid'
test.describe('Text input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultUrlInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'url-input-step'
import { generate } from 'short-uuid'
test.describe('Url input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -4,11 +4,11 @@ import {
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultGoogleAnalyticsOptions, IntegrationStepType } from 'models'
const typebotId = 'google-analytics-step'
import { generate } from 'short-uuid'
test.describe('Google Analytics step', () => {
test('its configuration should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@@ -2,10 +2,11 @@ import test, { expect, Page } from '@playwright/test'
import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { generate } from 'short-uuid'
test.describe.parallel('Google sheets integration', () => {
test('Insert row should work', async ({ page }) => {
const typebotId = 'google-sheets-insert'
const typebotId = generate()
await importTypebotInDatabase(
path.join(
__dirname,
@@ -20,10 +21,11 @@ test.describe.parallel('Google sheets integration', () => {
await page.click('text=Select an operation')
await page.click('text=Insert a row')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('button >> text="Email"')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('button >> text="Email" >> nth=1')
await page.click('text=Add a value')
await page.click('text=Select a column')
@@ -54,7 +56,7 @@ test.describe.parallel('Google sheets integration', () => {
})
test('Update row should work', async ({ page }) => {
const typebotId = 'google-sheets-update'
const typebotId = generate()
await importTypebotInDatabase(
path.join(
__dirname,
@@ -69,11 +71,13 @@ test.describe.parallel('Google sheets integration', () => {
await page.click('text=Select an operation')
await page.click('text=Update a row')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('button >> text="Email"')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('button >> text="Email" >> nth=1')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text=Last name')
await page.fill(
@@ -102,7 +106,7 @@ test.describe.parallel('Google sheets integration', () => {
})
test('Get row should work', async ({ page }) => {
const typebotId = 'google-sheets-get'
const typebotId = generate()
await importTypebotInDatabase(
path.join(
__dirname,
@@ -118,16 +122,16 @@ test.describe.parallel('Google sheets integration', () => {
await page.click('text=Get data from sheet')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('button >> text="Email"')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('button >> text="Email" >> nth=1')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="First name"')
await createNewVar(page, 'First name')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="Last name"')
await createNewVar(page, 'Last name')

View File

@@ -1,8 +1,9 @@
import test, { expect, Page } from '@playwright/test'
import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { generate } from 'short-uuid'
const typebotId = 'webhook-step'
const typebotId = generate()
test.describe('Webhook step', () => {
test('its configuration should work', async ({ page }) => {
@@ -23,6 +24,7 @@ test.describe('Webhook step', () => {
)
await page.click('text=Query params')
await page.click('text=Add a param')
await page.fill('input[placeholder="e.g. email"]', 'firstParam')
await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
@@ -34,6 +36,7 @@ test.describe('Webhook step', () => {
)
await page.click('text=Headers')
await page.click('text=Add a value')
await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
await page.fill(
'input[placeholder="e.g. application/json"]',
@@ -45,11 +48,8 @@ test.describe('Webhook step', () => {
await page.click('text=Variable values for test')
await addTestVariable(page, 'secret 1', 'secret1')
await page.click('text=Add an entry')
await addTestVariable(page, 'secret 2', 'secret2')
await page.click('text=Add an entry')
await addTestVariable(page, 'secret 3', 'secret3')
await page.click('text=Add an entry')
await addTestVariable(page, 'secret 4', 'secret4')
await page.click('text=Test the request')
@@ -58,12 +58,14 @@ test.describe('Webhook step', () => {
)
await page.click('text=Save in variables')
await page.click('text=Add an entry >> nth=-1')
await page.click('input[placeholder="Select the data"]')
await page.click('text=data[0].name')
})
})
const addTestVariable = async (page: Page, name: string, value: string) => {
await page.click('text=Add an entry')
await page.click('[data-testid="variables-input"] >> nth=-1')
await page.click(`text="${name}"`)
await page.fill('input >> nth=-1', value)

View File

@@ -1,9 +1,10 @@
import test, { expect, Page } from '@playwright/test'
import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import { generate } from 'short-uuid'
const typebotId = 'condition-step'
const typebotId = generate()
test.describe('Condition step', () => {
test('its configuration should work', async ({ page }) => {
@@ -16,6 +17,7 @@ test.describe('Condition step', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
await page.click('button:has-text("Add a comparison")')
await page.fill(
'input[placeholder="Search for a variable"] >> nth=-1',
'Age'
@@ -40,6 +42,7 @@ test.describe('Condition step', () => {
)
await page.click('text=Configure...')
await page.click('button:has-text("Add a comparison")')
await page.fill(
'input[placeholder="Search for a variable"] >> nth=-1',
'Age'

View File

@@ -2,8 +2,9 @@ import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import { generate } from 'short-uuid'
const typebotId = 'redirect-step'
const typebotId = generate()
test.describe('Redirect step', () => {
test('its configuration should work', async ({ page, context }) => {

View File

@@ -2,8 +2,9 @@ import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import { generate } from 'short-uuid'
const typebotId = 'set-variable-step'
const typebotId = generate()
test.describe('Set variable step', () => {
test('its configuration should work', async ({ page }) => {
@@ -24,7 +25,7 @@ test.describe('Set variable step', () => {
await page.click('text=Create "Total"')
await page.fill('textarea', '1000 * {{Num}}')
await page.click('text=Click to edit...')
await page.click('text=Click to edit...', { force: true })
await page.fill(
'input[placeholder="Select a variable"] >> nth=-1',
'Custom var'

View File

@@ -2,6 +2,7 @@ import test, { expect, Page } from '@playwright/test'
import { readFileSync } from 'fs'
import { InputStepType } from 'models'
import { parse } from 'papaparse'
import { generate } from 'short-uuid'
import {
createResults,
createTypebots,
@@ -9,7 +10,7 @@ import {
} from '../services/database'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
const typebotId = 'typebot-for-results'
const typebotId = generate()
test.describe('Results page', () => {
test('results should be deletable', async ({ page }) => {

View File

@@ -1,12 +1,13 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { generate } from 'short-uuid'
import { importTypebotInDatabase } from '../services/database'
import { typebotViewer } from '../services/selectorUtils'
test.describe.parallel('Settings page', () => {
test.describe('General', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'general-settings-typebot'
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
@@ -27,7 +28,7 @@ test.describe.parallel('Settings page', () => {
test.describe('Typing emulation', () => {
test('should be fillable', async ({ page }) => {
const typebotId = 'typing-emulation-typebot'
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
@@ -75,7 +76,7 @@ test.describe.parallel('Settings page', () => {
// Website image
const websiteImg = page.locator(':nth-match(img, 2)')
await expect(websiteImg).toHaveAttribute('src', '/viewer-preview.png')
await websiteImg.click()
await websiteImg.click({ position: { x: 0, y: 180 } })
await expect(page.locator('text=Giphy')).toBeHidden()
await page.click('button:has-text("Embed link")')
await page.fill('input[placeholder="Paste the image link..."]', imageUrl)

View File

@@ -1,12 +1,13 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { generate } from 'short-uuid'
import { importTypebotInDatabase } from '../services/database'
import { typebotViewer } from '../services/selectorUtils'
test.describe.parallel('Theme page', () => {
test.describe('General', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'general-theme-typebot'
const typebotId = generate()
const chatContainer = typebotViewer(page).locator(
'[data-testid="container"]'
)
@@ -106,7 +107,7 @@ test.describe.parallel('Theme page', () => {
test.describe('Custom CSS', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'custom-css-theme-typebot'
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{