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:
@ -1,88 +1,95 @@
|
||||
{
|
||||
"id": "typebot4",
|
||||
"createdAt": "2022-01-21T07:55:14.727Z",
|
||||
"updatedAt": "2022-01-21T07:55:14.727Z",
|
||||
"name": "My typebot",
|
||||
"ownerId": "user2",
|
||||
"id": "ckz478ggv1144eo1a5euf9twl",
|
||||
"createdAt": "2022-02-01T14:11:20.287Z",
|
||||
"updatedAt": "2022-02-01T14:11:20.286Z",
|
||||
"name": "Webhook",
|
||||
"ownerId": "ckz478eaj1091eo1amyo1me1z",
|
||||
"publishedTypebotId": null,
|
||||
"folderId": null,
|
||||
"webhooks": { "byId": {}, "allIds": [] },
|
||||
"blocks": {
|
||||
"byId": {
|
||||
"3kH2sUjVThQDWmqdoKnGk5": {
|
||||
"id": "3kH2sUjVThQDWmqdoKnGk5",
|
||||
"q7gjzJu7wBFycca5dNvZek": {
|
||||
"id": "q7gjzJu7wBFycca5dNvZek",
|
||||
"title": "Start",
|
||||
"stepIds": ["oxTsU2C1RX5QHuyY8qjHAM"],
|
||||
"graphCoordinates": { "x": 42, "y": 13 }
|
||||
"stepIds": ["da1KErxMzczHwaM25vQtFP"],
|
||||
"graphCoordinates": { "x": 0, "y": 0 }
|
||||
},
|
||||
"b9mSgu7RKmK4xuiTVQP5Me8": {
|
||||
"id": "b9mSgu7RKmK4xuiTVQP5Me8",
|
||||
"title": "Block #3",
|
||||
"stepIds": ["ssLd2wjExS9qWRur4tZDU1Z"],
|
||||
"graphCoordinates": { "x": 300, "y": 550 }
|
||||
"bUUqjxyAFZkKzjByqEaEzV": {
|
||||
"id": "bUUqjxyAFZkKzjByqEaEzV",
|
||||
"graphCoordinates": { "x": 248, "y": 247 },
|
||||
"title": "Block 1",
|
||||
"stepIds": ["siAj9x5LZ8W5cviqznX82T3", "s5GToHCtqZhwpygDuTb3tu4"]
|
||||
},
|
||||
"bdFW2HHjMoEFmqHtFre9Xi8": {
|
||||
"id": "bdFW2HHjMoEFmqHtFre9Xi8",
|
||||
"title": "Block #2",
|
||||
"stepIds": ["sgkADMK25y9P9V3vjwjBaac", "ssEiEECKSFkA44dGDceHxKw"],
|
||||
"graphCoordinates": { "x": 121, "y": 227 }
|
||||
"ifpYvoBnYU2X3B3RgwfeNJ": {
|
||||
"id": "ifpYvoBnYU2X3B3RgwfeNJ",
|
||||
"graphCoordinates": { "x": 690, "y": 504 },
|
||||
"title": "Block 2",
|
||||
"stepIds": ["sjDhaBWVLd2Ep7N3WryJGQJ"]
|
||||
}
|
||||
},
|
||||
"allIds": [
|
||||
"3kH2sUjVThQDWmqdoKnGk5",
|
||||
"bdFW2HHjMoEFmqHtFre9Xi8",
|
||||
"b9mSgu7RKmK4xuiTVQP5Me8"
|
||||
"q7gjzJu7wBFycca5dNvZek",
|
||||
"bUUqjxyAFZkKzjByqEaEzV",
|
||||
"ifpYvoBnYU2X3B3RgwfeNJ"
|
||||
]
|
||||
},
|
||||
"steps": {
|
||||
"byId": {
|
||||
"oxTsU2C1RX5QHuyY8qjHAM": {
|
||||
"id": "oxTsU2C1RX5QHuyY8qjHAM",
|
||||
"da1KErxMzczHwaM25vQtFP": {
|
||||
"id": "da1KErxMzczHwaM25vQtFP",
|
||||
"type": "start",
|
||||
"label": "Start",
|
||||
"edgeId": "25yX9DnQgdafpdAjfAu5Fp",
|
||||
"blockId": "3kH2sUjVThQDWmqdoKnGk5"
|
||||
"blockId": "q7gjzJu7wBFycca5dNvZek",
|
||||
"edgeId": "mcxdssnDkbvJBZ6d51XDey"
|
||||
},
|
||||
"sgkADMK25y9P9V3vjwjBaac": {
|
||||
"id": "sgkADMK25y9P9V3vjwjBaac",
|
||||
"s5GToHCtqZhwpygDuTb3tu4": {
|
||||
"id": "s5GToHCtqZhwpygDuTb3tu4",
|
||||
"blockId": "bUUqjxyAFZkKzjByqEaEzV",
|
||||
"type": "choice input",
|
||||
"options": {
|
||||
"buttonLabel": "Send",
|
||||
"isMultipleChoice": false,
|
||||
"itemIds": ["ddSjZkft27gQnZAEeXtQny"]
|
||||
}
|
||||
},
|
||||
"siAj9x5LZ8W5cviqznX82T3": {
|
||||
"id": "siAj9x5LZ8W5cviqznX82T3",
|
||||
"blockId": "bUUqjxyAFZkKzjByqEaEzV",
|
||||
"type": "text",
|
||||
"blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
|
||||
"content": {
|
||||
"html": "<div>Ready?</div>",
|
||||
"richText": [{ "type": "p", "children": [{ "text": "Ready?" }] }],
|
||||
"plainText": "Ready?"
|
||||
}
|
||||
},
|
||||
"ssEiEECKSFkA44dGDceHxKw": {
|
||||
"id": "ssEiEECKSFkA44dGDceHxKw",
|
||||
"type": "choice input",
|
||||
"edgeId": "oxEEtym3NfDf34NCipzjRQ",
|
||||
"blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
|
||||
"options": { "itemIds": ["q69Ex7LacPrH9QUMeosRnB"] }
|
||||
},
|
||||
"ssLd2wjExS9qWRur4tZDU1Z": {
|
||||
"id": "ssLd2wjExS9qWRur4tZDU1Z",
|
||||
"sjDhaBWVLd2Ep7N3WryJGQJ": {
|
||||
"id": "sjDhaBWVLd2Ep7N3WryJGQJ",
|
||||
"blockId": "ifpYvoBnYU2X3B3RgwfeNJ",
|
||||
"type": "Webhook",
|
||||
"blockId": "b9mSgu7RKmK4xuiTVQP5Me8",
|
||||
"options": { "webhookId": "4h4Kk3Q1qGy7gFzpZtWVpU" }
|
||||
"options": {
|
||||
"responseVariableMapping": { "byId": {}, "allIds": [] },
|
||||
"variablesForTest": { "byId": {}, "allIds": [] },
|
||||
"webhookId": "3nxQGoMMXpA6K5iuhGFW5S"
|
||||
}
|
||||
}
|
||||
},
|
||||
"allIds": [
|
||||
"oxTsU2C1RX5QHuyY8qjHAM",
|
||||
"sgkADMK25y9P9V3vjwjBaac",
|
||||
"ssEiEECKSFkA44dGDceHxKw",
|
||||
"ssLd2wjExS9qWRur4tZDU1Z"
|
||||
"da1KErxMzczHwaM25vQtFP",
|
||||
"s5GToHCtqZhwpygDuTb3tu4",
|
||||
"siAj9x5LZ8W5cviqznX82T3",
|
||||
"sjDhaBWVLd2Ep7N3WryJGQJ"
|
||||
]
|
||||
},
|
||||
"choiceItems": {
|
||||
"byId": {
|
||||
"q69Ex7LacPrH9QUMeosRnB": {
|
||||
"id": "q69Ex7LacPrH9QUMeosRnB",
|
||||
"stepId": "ssEiEECKSFkA44dGDceHxKw",
|
||||
"content": "Go"
|
||||
"ddSjZkft27gQnZAEeXtQny": {
|
||||
"id": "ddSjZkft27gQnZAEeXtQny",
|
||||
"stepId": "s5GToHCtqZhwpygDuTb3tu4",
|
||||
"content": "Go",
|
||||
"edgeId": "x6cbRGrLAVYy4ymAg5tfp9"
|
||||
}
|
||||
},
|
||||
"allIds": ["q69Ex7LacPrH9QUMeosRnB"]
|
||||
"allIds": ["ddSjZkft27gQnZAEeXtQny"]
|
||||
},
|
||||
"variables": {
|
||||
"byId": {
|
||||
@ -112,30 +119,36 @@
|
||||
},
|
||||
"webhooks": {
|
||||
"byId": {
|
||||
"4h4Kk3Q1qGy7gFzpZtWVpU": { "id": "4h4Kk3Q1qGy7gFzpZtWVpU", "url": "" }
|
||||
"3nxQGoMMXpA6K5iuhGFW5S": {
|
||||
"id": "3nxQGoMMXpA6K5iuhGFW5S",
|
||||
"method": "GET",
|
||||
"headers": { "byId": {}, "allIds": [] },
|
||||
"queryParams": { "byId": {}, "allIds": [] }
|
||||
}
|
||||
},
|
||||
"allIds": ["4h4Kk3Q1qGy7gFzpZtWVpU"]
|
||||
"allIds": ["3nxQGoMMXpA6K5iuhGFW5S"]
|
||||
},
|
||||
"edges": {
|
||||
"byId": {
|
||||
"25yX9DnQgdafpdAjfAu5Fp": {
|
||||
"id": "25yX9DnQgdafpdAjfAu5Fp",
|
||||
"to": { "blockId": "bdFW2HHjMoEFmqHtFre9Xi8" },
|
||||
"mcxdssnDkbvJBZ6d51XDey": {
|
||||
"from": {
|
||||
"stepId": "oxTsU2C1RX5QHuyY8qjHAM",
|
||||
"blockId": "3kH2sUjVThQDWmqdoKnGk5"
|
||||
}
|
||||
"blockId": "q7gjzJu7wBFycca5dNvZek",
|
||||
"stepId": "da1KErxMzczHwaM25vQtFP"
|
||||
},
|
||||
"to": { "blockId": "bUUqjxyAFZkKzjByqEaEzV" },
|
||||
"id": "mcxdssnDkbvJBZ6d51XDey"
|
||||
},
|
||||
"oxEEtym3NfDf34NCipzjRQ": {
|
||||
"id": "oxEEtym3NfDf34NCipzjRQ",
|
||||
"to": { "blockId": "b9mSgu7RKmK4xuiTVQP5Me8" },
|
||||
"x6cbRGrLAVYy4ymAg5tfp9": {
|
||||
"from": {
|
||||
"stepId": "ssEiEECKSFkA44dGDceHxKw",
|
||||
"blockId": "bdFW2HHjMoEFmqHtFre9Xi8"
|
||||
}
|
||||
"blockId": "bUUqjxyAFZkKzjByqEaEzV",
|
||||
"stepId": "s5GToHCtqZhwpygDuTb3tu4",
|
||||
"buttonId": "ddSjZkft27gQnZAEeXtQny"
|
||||
},
|
||||
"to": { "blockId": "ifpYvoBnYU2X3B3RgwfeNJ" },
|
||||
"id": "x6cbRGrLAVYy4ymAg5tfp9"
|
||||
}
|
||||
},
|
||||
"allIds": ["25yX9DnQgdafpdAjfAu5Fp", "oxEEtym3NfDf34NCipzjRQ"]
|
||||
"allIds": ["mcxdssnDkbvJBZ6d51XDey", "x6cbRGrLAVYy4ymAg5tfp9"]
|
||||
},
|
||||
"theme": {
|
||||
"chat": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "typebot4",
|
||||
"id": "bdFW2HHjMoEFmqHtFre9Xi8",
|
||||
"createdAt": "2022-01-21T07:55:14.727Z",
|
||||
"updatedAt": "2022-01-21T07:55:14.727Z",
|
||||
"name": "My typebot",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "typebot4",
|
||||
"id": "bdFW2HHjMoEFmqHtFre9Xi8",
|
||||
"createdAt": "2022-01-21T07:55:14.727Z",
|
||||
"updatedAt": "2022-01-21T07:55:14.727Z",
|
||||
"name": "My typebot",
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { chromium, FullConfig, Page } from '@playwright/test'
|
||||
import { existsSync } from 'fs'
|
||||
import { setupDatabase, teardownDatabase } from './services/database'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@ -10,12 +11,14 @@ async function globalSetup(config: FullConfig) {
|
||||
|
||||
await teardownDatabase()
|
||||
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage()
|
||||
await signIn(page)
|
||||
await page.context().storageState({
|
||||
path: './playwright/authenticatedState.json',
|
||||
})
|
||||
if (!existsSync('./playwright/authenticatedState.json')) {
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage()
|
||||
await signIn(page)
|
||||
await page.context().storageState({
|
||||
path: './playwright/authenticatedState.json',
|
||||
})
|
||||
}
|
||||
|
||||
await setupDatabase(process.env.GITHUB_EMAIL as string)
|
||||
}
|
||||
@ -23,7 +26,7 @@ async function globalSetup(config: FullConfig) {
|
||||
const signIn = async (page: Page) => {
|
||||
if (!process.env.GITHUB_EMAIL || !process.env.GITHUB_PASSWORD)
|
||||
throw new Error(
|
||||
'GITHUB_USERNAME or GITHUB_PASSWORD are missing in the environment. They are required to log in.'
|
||||
'GITHUB_EMAIL or GITHUB_PASSWORD are missing in the environment. They are required to log in.'
|
||||
)
|
||||
await page.goto(`${process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL}/signin`)
|
||||
await page.click('text=Continue with GitHub')
|
||||
|
@ -10,7 +10,11 @@ import { readFileSync } from 'fs'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export const teardownDatabase = async () => prisma.user.deleteMany()
|
||||
export const teardownDatabase = async () => {
|
||||
await prisma.credentials.deleteMany()
|
||||
await prisma.dashboardFolder.deleteMany()
|
||||
return prisma.typebot.deleteMany()
|
||||
}
|
||||
|
||||
export const setupDatabase = async (userEmail: string) => {
|
||||
const createdUser = await getSignedInUser(userEmail)
|
||||
@ -38,7 +42,6 @@ export const createFolders = (partialFolders: Partial<DashboardFolder>[]) =>
|
||||
data: partialFolders.map((folder) => ({
|
||||
ownerId: process.env.PLAYWRIGHT_USER_ID as string,
|
||||
name: 'Folder #1',
|
||||
id: 'folder',
|
||||
...folder,
|
||||
})),
|
||||
})
|
||||
@ -56,7 +59,7 @@ const createCredentials = () =>
|
||||
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
|
||||
// This token is linked to a mock Google account (typebot.test.user@gmail.com)
|
||||
refresh_token:
|
||||
'1//03W5-TyIxXd7nCgYIARAAGAMSNwF-L9IrAGAmp5MG8RqVyk6YYmqDDn9x-4nHTkSUj4xZWuMs6mNeyjdS_bgO0CWuZEfJoAd_zIw',
|
||||
'1//0379tIHBxszeXCgYIARAAGAMSNwF-L9Ir0zhkzhblwXqn3_jYqRP3pajcUpqkjRU3fKZZ_eQakOa28amUHSQ-Q9fMzk89MpRTvkc',
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -117,15 +120,6 @@ const parseTypebotToPublicTypebot = (
|
||||
edges: typebot.edges,
|
||||
})
|
||||
|
||||
export const loadRawTypebotInDatabase = (typebot: Typebot) =>
|
||||
prisma.typebot.create({
|
||||
data: {
|
||||
...typebot,
|
||||
id: 'typebot4',
|
||||
ownerId: process.env.PLAYWRIGHT_USER_ID,
|
||||
} as any,
|
||||
})
|
||||
|
||||
const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => ({
|
||||
id: partialTypebot.id ?? 'typebot',
|
||||
folderId: null,
|
||||
@ -216,14 +210,12 @@ export const importTypebotInDatabase = (
|
||||
path: string,
|
||||
updates?: Partial<Typebot>
|
||||
) => {
|
||||
const typebot: Typebot = JSON.parse(readFileSync(path).toString())
|
||||
try {
|
||||
return prisma.typebot.create({
|
||||
data: {
|
||||
...typebot,
|
||||
...updates,
|
||||
ownerId: process.env.PLAYWRIGHT_USER_ID,
|
||||
} as any,
|
||||
})
|
||||
} catch {}
|
||||
const typebot: any = {
|
||||
...JSON.parse(readFileSync(path).toString()),
|
||||
...updates,
|
||||
ownerId: process.env.PLAYWRIGHT_USER_ID,
|
||||
}
|
||||
return prisma.typebot.create({
|
||||
data: typebot,
|
||||
})
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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")',
|
||||
|
28
apps/builder/playwright/tests/editor.spec.ts
Normal file
28
apps/builder/playwright/tests/editor.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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 }) => {
|
||||
|
@ -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'
|
||||
|
@ -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 }) => {
|
||||
|
@ -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)
|
||||
|
@ -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'),
|
||||
{
|
||||
|
Reference in New Issue
Block a user