feat(ui/tracker): create integrated e2e testing for tracker and player

* fix(ui): fix create note modal

* change(ui): remove tests for a moment

* change(ui): fix job name

* change(ui): fix job action

* change(ui): fix job action

* change(ui): fix job action

* change(ui): fix job action

* change(ui): add testing public dir

* change(ui): return silent server start

* change(ui): fix session url

* change(ui): fix session url

* change(ui): fix session url

* change(ui): fix mockup

* change(ui): fix mockup

* change(ui): fix snapshot images

* change(ui): fix snapshot images

* change(ui): fix testing app

* change(ui): fix testing app

* change(ui): fix testing flow and add loggs

* change(ui): add network and events

* change(ui): fix table view

* change(ui): fix table view

* change(ui): fix acc req intercept

* change(ui): fix mob req intercept

* change(ui): change jump method
This commit is contained in:
Delirium 2023-02-03 16:58:08 +01:00 committed by GitHub
parent 35a282a510
commit 7dec39300f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 12674 additions and 63 deletions

View file

@ -7,34 +7,67 @@ on:
branches: [ "main" ]
paths:
- frontend/**
- tracker/**
pull_request:
branches: [ "dev", "main" ]
paths:
- frontend/**
- tracker/**
env:
API: ${{ secrets.E2E_API_ORIGIN }}
ASSETS: ${{ secrets.E2E_ASSETS_ORIGIN }}
APIEDP: ${{ secrets.E2E_EDP_ORIGIN }}
CY_ACC: ${{ secrets.CYPRESS_ACCOUNT }}
CY_PASS: ${{ secrets.CYPRESS_PASSWORD }}
FOSS_PROJECT_KEY: ${{ secrets.FOSS_PROJECT_KEY }}
FOSS_INGEST: ${{ secrets.FOSS_INGEST }}
jobs:
build:
runs-on: ubuntu-22.04
name: Visual Testing
defaults:
run:
working-directory: ./frontend
build-and-test:
runs-on: macos-latest
name: Build and test Tracker plus Replayer
strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [ 16.x ]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Cache tracker modules
uses: actions/cache@v1
with:
path: tracker/tracker/node_modules
key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
test_tracker_build{{ runner.OS }}-build-
test_tracker_build{{ runner.OS }}-
- name: Setup Testing packages
run: |
cd tracker/tracker
npm i -g yarn
yarn
# - name: Jest tests
# run: yarn test
- name: Build tracker inst
run: |
cd tracker/tracker
yarn build
- name: Setup Testing UI Env
run: |
cd tracker/tracker-testing-playground
echo "REACT_APP_KEY=$FOSS_PROJECT_KEY" >> .env
echo "REACT_APP_INGEST=$FOSS_INGEST" >> .env
- name: Setup Testing packages
run: |
cd tracker/tracker-testing-playground
yarn
- name: Wait for Testing Frontend
run: |
cd tracker/tracker-testing-playground
yarn start &> ui.log &
npx wait-on http://localhost:3000
cd ../../frontend
- name: Cache node modules
uses: actions/cache@v1
with:
@ -45,6 +78,7 @@ jobs:
${{ runner.OS }}-
- name: Setup env
run: |
cd frontend
echo "NODE_ENV=development" >> .env
echo "SOURCEMAP=true" >> .env
echo "ORIGIN=$API" >> .env
@ -66,21 +100,29 @@ jobs:
- name: Setup packages
run: |
npm i -g yarn
cd frontend
yarn
- name: Run unit tests
run: yarn test
run: |
cd frontend
yarn test
- name: Run Frontend
run: yarn start &> server.log &
run: |
cd frontend
yarn start &> frontend.log &
- name: Wait for frontend
run: npx wait-on http://0.0.0.0:3333
run: |
cd frontend
npx wait-on http://0.0.0.0:3333
- name: (Chrome) Run visual tests
run: yarn cy:test
run: |
cd frontend
yarn cy:test
# firefox have different viewport somehow
# - name: (Firefox) Run visual tests
# run: yarn cy:test-firefox
- name: (Edge) Run visual tests
run: yarn cy:test-edge
# - name: (Edge) Run visual tests
# run: yarn cy:test-edge
- name: Upload Debug
if: ${{ failure() }}
uses: actions/upload-artifact@v3

View file

@ -103,6 +103,7 @@ class Login extends React.Component {
<div className="mb-6">
<label>Email</label>
<Input
data-test-id={"login"}
autoFocus={true}
autoComplete="username"
type="text"
@ -116,6 +117,7 @@ class Login extends React.Component {
<div className="mb-6">
<label className="mb-2">Password</label>
<Input
data-test-id={"password"}
autoComplete="current-password"
type="password"
placeholder="Password"
@ -141,7 +143,7 @@ class Login extends React.Component {
</div>
) : null}
{/* <div className={ stl.formFooter }> */}
<Button className="mt-2" type="submit" variant="primary">
<Button data-test-id={"log-button"} className="mt-2" type="submit" variant="primary">
{'Login'}
</Button>

View file

@ -63,6 +63,9 @@ export default class WebPlayer extends Player {
endTime, // : 0,
})
// @ts-ignore
window.playerJumpToTime = this.jump.bind(this)
}
attach = (parent: HTMLElement, isClickmap?: boolean) => {

View file

@ -1,5 +1,8 @@
import { defineConfig } from "cypress";
import {addMatchImageSnapshotPlugin} from 'cypress-image-snapshot/plugin';
const data = {}
export default defineConfig({
e2e: {
viewportHeight: 900,
@ -8,6 +11,15 @@ export default defineConfig({
setupNodeEvents(on, config) {
// implement node event listeners here
addMatchImageSnapshotPlugin(on, config)
on('task', {
setValue({key, value}) {
data[key] = value
return null
},
getValue(key) {
return data[key] || null
},
})
},
}
});

View file

@ -3,12 +3,12 @@ describe('Testing general stability', {
viewportWidth: 1400,
}, () => {
it('Checking if app will crash', () => {
cy.intercept('/api/account').as('getAccount')
cy.intercept('**/api/account').as('getAccount');
cy.visit('/')
cy.get(':nth-child(1) > .relative > .p-2').type(Cypress.env('account').replaceAll("\"", ''))
cy.get(':nth-child(2) > .relative > .p-2').type(Cypress.env('password').replaceAll("\"", ''))
cy.get('.justify-center > .h-10').click()
cy.get('[data-test-id=login]').type(Cypress.env('account').replaceAll('"', ''));
cy.get('[data-test-id=password]').type(Cypress.env('password').replaceAll('"', ''));
cy.get('[data-test-id=log-button]').click();
cy.wait('@getAccount')
cy.get('#search').should('be.visible')

View file

@ -1,44 +1,150 @@
describe('Replayer visual match test', {
const SECOND = 1000;
describe(
'Replayer visual match test',
{
viewportHeight: 900,
viewportWidth: 1400,
}, () => {
},
() => {
it('Generating tracker session...', () => {
cy.intercept('**/api/account').as('getAccount');
cy.intercept('**/dom.mobs?*').as('getFirstMob')
cy.visit('http://0.0.0.0:3333', {
onBeforeLoad: function (window) {
window.localStorage.setItem('notesFeatureViewed', 'true');
},
});
cy.origin('http://localhost:3000/', { args: { SECOND } }, ({ SECOND }) => {
cy.visit('/');
cy.wait(SECOND * 3);
cy.get('#get-table').click()
cy.wait(SECOND * 3);
cy.get('#testrender').click();
cy.wait(SECOND * 3);
cy.get('#testrender').click();
cy.get('#get-main').click()
cy.get('#obscured-text').type('testing typing in obscured input');
cy.get('#visible-input').type('testing typing in visible input');
cy.wait(SECOND * 3);
cy.get('#redcounter').click().click().click();
cy.get('#test-api').click().click();
cy.get('#test-event').click().click();
cy.wait(SECOND * 3);
cy.log('finished generating a session')
cy.window().then((window) => {
cy.task('setValue', { key: 'url', value: window.__OPENREPLAY__.app.getSessionURL() });
cy.log(window.__OPENREPLAY__.app.getSessionURL());
cy.wait(SECOND * 3)
});
});
cy.task('getValue', 'url').as('firstAlias');
cy.visit('/');
cy.get('[data-test-id=login]').type(Cypress.env('account').replaceAll('"', ''));
cy.get('[data-test-id=password]').type(Cypress.env('password').replaceAll('"', ''));
cy.get('[data-test-id=log-button]').click();
cy.wait('@getAccount');
// checking real session
cy.get('@firstAlias').then((firstAlias) => {
cy.log(firstAlias);
cy.log('waiting for session to save')
cy.wait(SECOND * 180);
cy.visit(firstAlias.slice(27) + '?freeze=true');
cy.log('loading session')
cy.wait(SECOND * 25);
cy.window().then(win => {
const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime
jumpMethod(SECOND * 3)
})
cy.wait(SECOND * 3);
cy.matchImageSnapshot('Tracker-3');
cy.window().then(win => {
win.playerJump(SECOND * 6)
})
cy.wait(SECOND * 3);
cy.matchImageSnapshot('Tracker-5');
cy.window().then(win => {
const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime
jumpMethod(SECOND * 9)
})
cy.wait(SECOND * 3);
cy.matchImageSnapshot('Tracker-9');
cy.window().then(win => {
const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime
jumpMethod(SECOND * 20)
})
cy.wait(SECOND * 3);
cy.get('#control-button-redux > .controlButton-module__label--YznMl').click()
cy.wait(SECOND * 0.5)
cy.matchImageSnapshot('Tracker-19-redux');
cy.get('#control-button-network').click()
cy.wait(SECOND * 0.5)
cy.matchImageSnapshot('Tracker-19-network');
cy.get('#control-button-events').click()
cy.wait(SECOND * 0.5)
cy.matchImageSnapshot('Tracker-19-events');
cy.log('custom session test success')
});
});
it('Checking Replayer at breakpoints, user events and console', () => {
cy.intercept('**/api/account').as('getAccount')
cy.intercept('**/mobs/7585361734083637/dom.mobs?*').as('getFirstMob')
cy.intercept('**/mobs/7585361734083637/dom.mobe?*').as('getSecondMob')
cy.log('testing premade session')
cy.visit('/', {
cy.visit('http://0.0.0.0:3333', {
onBeforeLoad: function (window) {
window.localStorage.setItem('notesFeatureViewed', 'true');
}
})
cy.get(':nth-child(1) > .relative > .p-2').type(Cypress.env('account').replaceAll("\"", ''))
cy.get(':nth-child(2) > .relative > .p-2').type(Cypress.env('password').replaceAll("\"", ''))
cy.get('.justify-center > .h-10').click()
cy.get('[data-test-id=login]').type(Cypress.env('account').replaceAll('"', ''));
cy.get('[data-test-id=password]').type(Cypress.env('password').replaceAll('"', ''));
cy.get('[data-test-id=log-button]').click();
cy.wait('@getAccount')
cy.wait(2000)
cy.wait(SECOND * 2)
cy.visit('3/session/7585361734083637?jumpto=5000&freeze=true')
cy.wait('@getFirstMob')
cy.wait('@getSecondMob')
cy.wait(2000)
cy.window().then(win => {
win.playerJump(5000)
})
cy.wait(4000)
cy.wait(SECOND * 2)
cy.window().then(win => {
const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime
jumpMethod(SECOND * 5)
})
cy.wait(SECOND * 4)
cy.matchImageSnapshot('1st-breakpoint');
cy.window().then(win => {
win.playerJump(21000)
const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime
jumpMethod(SECOND * 21)
})
cy.wait(4000)
cy.wait(SECOND * 4)
cy.matchImageSnapshot('2nd-breakpoint');
cy.get('[data-openreplay-label="User Steps"]').click()
cy.wait(SECOND * 0.5)
cy.matchImageSnapshot('User-Events');
cy.get('#control-button-network > .controlButton-module__label--YznMl').click()
cy.wait(SECOND * 0.5)
cy.matchImageSnapshot('Network-Events');
})
})
}
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 179 KiB

View file

@ -0,0 +1,4 @@
.env
build
node_modules
!public

View file

@ -0,0 +1 @@
# Create React App

View file

@ -0,0 +1,51 @@
{
"name": "with-create-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@openreplay/tracker": "file:../../../openreplay/tracker/tracker",
"@openreplay/tracker-axios": "3.6.1",
"@openreplay/tracker-fetch": "3.6.1",
"@openreplay/tracker-redux": "3.5.1",
"@openreplay/tracker-zustand": "1.0.2",
"@tanstack/react-table": "^8.2.6",
"@types/node": "^12.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/uuid": "^8.3.3",
"axios": "^0.27.2",
"react": "^17.0.2",
"react-bootstrap": "^2.5.0",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"react-table": "^7.8.0",
"redux": "^4.2.0",
"typescript": "^4.1.2",
"uuid": "^8.3.2",
"zustand": "^4.1.1"
},
"scripts": {
"start": "HOST=0.0.0.0 react-scripts start",
"build": "NODE_OPTIONS=--openssl-legacy-provider react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View file

@ -0,0 +1,37 @@
.App {
text-align: center;
}
.App-logo {
height: 40px;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -0,0 +1,455 @@
import React from "react";
import Tracker from "@openreplay/tracker";
import axios from "axios";
import create from "zustand";
import { userId, getTracker, store } from "./tracker";
import logo from "./logo.svg";
import "./App.css";
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
const trackerEx = getTracker();
function App() {
const [view, setView] = React.useState("main");
const [tracker, setTracker] = React.useState<Tracker>();
const [counter, setCounter] = React.useState(store.getState().value);
const [data, setData] = React.useState(() => [...defaultData]);
const [shouldRerender, setShould] = React.useState(false);
const [sRen, setRen] = React.useState(true);
const [sUrl, setURL] = React.useState("");
const rerender = React.useReducer(() => ({}), {})[1];
const [input, setInput] = React.useState("");
store.subscribe(() => setCounter(store.getState().value));
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
const chasd = () => {
data.forEach((i) => (i.age = Math.floor(Math.random() * 100)));
console.log(data[0]);
setData(data);
rerender();
};
React.useEffect(() => {
trackerEx.start().then((session) => {
console.log(session);
const url = trackerEx.getSessionURL();
setURL(url || "");
setTracker(trackerEx);
});
}, []);
React.useEffect(() => {
const id = setInterval(() => shouldRerender && rerender(), 5000);
return () => clearInterval(id);
}, [rerender, shouldRerender]);
if (!sRen)
return (
<div>
<button id="testrender" onClick={() => setRen(true)}>
test
</button>
test
</div>
);
const testAPI = () => {
fetch("https://pokeapi.co/api/v2/pokemon/ditto")
.then((r) => r.json())
.then((p) => console.log(p));
};
const testAPIError = () => {
fetch("https://pokeapi.co/api/v2/poakemon/ditto")
.then((r) => r.json())
.then((p) => console.log(p));
};
const incrementRedux = () => {
store.dispatch({ type: "counter/incremented" });
};
const redux2 = () => {
store.dispatch({ type: "counter/test" });
};
const redux3 = () => {
store.dispatch({ type: "counter/test2" });
};
const redux4 = () => {
store.dispatch({ type: "counter/test3" });
};
const customEvent = () => {
tracker?.event("test", "event");
};
const customError = () => {
tracker?.handleError(new Error(), { testing: "stuff", taha: "is cool" });
};
const testJSError = () => {
throw new Error("Im the error");
};
const axiosInst = axios.create();
const addAxios = () => {
console.log("hull");
};
const testAxiosApi = () => {
axiosInst("https://pokeapi.co/api/v2/pokemon/ditto").then((p) =>
console.log(p)
);
};
return (
<>
<button id="testrender" onClick={() => setRen(false)}>
test rerender
</button>
<button onClick={testAPI} id={"test-api"}>test api</button>
<button onClick={testAPIError}>test api error</button>
<button id="redcounter" onClick={incrementRedux}>
test Redux {counter}
</button>
<button onClick={customEvent} id={"test-event"}>test custom event</button>
<button onClick={customError}>test custom tags error</button>
<button onClick={addAxios}>add axios</button>
<button onClick={testAxiosApi}>test axios</button>
<a href="https://google.com">test link</a>
<br />
<button onClick={redux2}>test Redux {counter}</button>
<button onClick={redux3}>test Redux {counter}</button>
<button onClick={redux4}>test Redux {counter}</button>
<button onClick={testJSError}>JS Error</button>
<br />
<button id={"get-main"} onClick={() => setView('main')}>main</button>
<button id={"get-table"} onClick={() => setView('table')}>table</button>
{view} view
<div className="App">
{view === "main" ? (
<header className="App-header">
{/* <iframe src="https://fr.wikipedia.org/wiki/Main_Page" width="640" height="480" title="testing"></iframe> */}
<img src={logo} className="App-logo" alt="logo" />
<span>Your userId is [{userId}]</span>
<span>
session url:{" "}
<a rel="noreferrer noopener" target="_blank" href={sUrl}>
{sUrl}
</a>
</span>
<input
id="visible-input"
value={input}
onChange={(e) => setInput(e.target.value)}
type="text"
/>
<div className="testhide"> should not be seen here</div>
<input
className="testobscure"
placeholder="test"
id="testobscured"
></input>
<div data-openreplay-obscured id="obscured-div">
obscured
</div>
<div data-openreplay-masked id="masked-div">
masked deprecated
</div>
<input
data-openreplay-obscured
type="text"
id="obscured-text"
placeholder="obscured text"
></input>
</header>
) : (
<div className="p-2" style={{ display: 'flex'}}>
<table>
<thead>
{table
.getHeaderGroups()
.map(
(headerGroup: {
id: React.Key | null | undefined;
headers: any[];
}) => (
<tr key={headerGroup.id + Math.random().toString(36)}>
{headerGroup.headers.map(
(header: {
id: React.Key | null | undefined;
isPlaceholder: any;
column: { columnDef: { header: any } };
getContext: () => any;
}) => (
<th key={header.id + Math.random().toString(36)}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
)
)}
</tr>
)
)}
</thead>
<tbody>
{table
.getRowModel()
.rows.map(
(row: {
id: React.Key | null | undefined;
getVisibleCells: () => any[];
}) => (
<tr key={row.id + Math.random().toString(36)}>
{row
.getVisibleCells()
.map(
(cell: {
id: React.Key | null | undefined;
column: { columnDef: { cell: any } };
getContext: () => any;
}) => (
<td key={cell.id + Math.random().toString(36)}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
)
)}
</tr>
)
)}
</tbody>
<tfoot>
{table
.getFooterGroups()
.map(
(footerGroup: {
id: React.Key | null | undefined;
headers: any[];
}) => (
<tr key={footerGroup.id + Math.random().toString(36)}>
{footerGroup.headers.map(
(header: {
id: React.Key | null | undefined;
isPlaceholder: any;
column: { columnDef: { footer: any } };
getContext: () => any;
}) => (
<th key={header.id + Math.random().toString(36)}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.footer,
header.getContext()
)}
</th>
)
)}
</tr>
)
)}
</tfoot>
</table>
<table>
<thead>
{table
.getHeaderGroups()
.map(
(headerGroup: {
id: React.Key | null | undefined;
headers: any[];
}) => (
<tr key={headerGroup.id + Math.random().toString(36)}>
{headerGroup.headers.map(
(header: {
id: React.Key | null | undefined;
isPlaceholder: any;
column: { columnDef: { header: any } };
getContext: () => any;
}) => (
<th key={header.id + Math.random().toString(36)}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
)
)}
</tr>
)
)}
</thead>
<tbody>
{table
.getRowModel()
.rows.map(
(row: {
id: React.Key | null | undefined;
getVisibleCells: () => any[];
}) => (
<tr key={row.id + Math.random().toString(36)}>
{row
.getVisibleCells()
.map(
(cell: {
id: React.Key | null | undefined;
column: { columnDef: { cell: any } };
getContext: () => any;
}) => (
<td key={cell.id + Math.random().toString(36)}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
)
)}
</tr>
)
)}
</tbody>
<tfoot>
{table
.getFooterGroups()
.map(
(footerGroup: {
id: React.Key | null | undefined;
headers: any[];
}) => (
<tr key={footerGroup.id + Math.random().toString(36)}>
{footerGroup.headers.map(
(header: {
id: React.Key | null | undefined;
isPlaceholder: any;
column: { columnDef: { footer: any } };
getContext: () => any;
}) => (
<th key={header.id + Math.random().toString(36)}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.footer,
header.getContext()
)}
</th>
)
)}
</tr>
)
)}
</tfoot>
</table>
<div className="h-4" />
</div>
)}
</div>
</>
);
}
export default App;
type Person = {
firstName: string;
lastName: string;
age: number;
visits: number;
status: string;
progress: number;
};
const defaultData: Person[] = [
{
firstName: "tanner",
lastName: "linsley",
age: Math.floor(Math.random() * 100),
visits: 100,
status: "In Relationship",
progress: 50,
},
{
firstName: "tandy",
lastName: "miller",
age: 40,
visits: 40,
status: "Single",
progress: 80,
},
{
firstName: "joe",
lastName: "dirte",
age: 45,
visits: 20,
status: "Complicated",
progress: 10,
},
];
const testArr = [...defaultData];
for (let i = 0; i < 30; i++) {
defaultData.push(...testArr);
}
const columns: ColumnDef<Person>[] = [
{
accessorKey: "firstName",
cell: (info: { getValue: () => any }) => info.getValue(),
footer: (info: { column: { id: any } }) => info.column.id,
},
{
accessorFn: (row: { lastName: any }) => row.lastName,
id: "lastName",
cell: (info: {
getValue: () =>
| boolean
| React.ReactChild
| React.ReactFragment
| React.ReactPortal
| null
| undefined;
}) => <i>{info.getValue()}</i>,
header: () => <span>Last Name</span>,
footer: (info: { column: { id: any } }) => info.column.id,
},
{
accessorKey: "age",
header: () => "Age",
footer: (info: { column: { id: any } }) => info.column.id,
},
{
accessorKey: "visits",
header: () => <span>Visits</span>,
footer: (info: { column: { id: any } }) => info.column.id,
},
{
accessorKey: "status",
header: "Status",
footer: (info: { column: { id: any } }) => info.column.id,
},
{
accessorKey: "progress",
header: "Profile Progress",
footer: (info: { column: { id: any } }) => info.column.id,
},
];

View file

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View file

@ -0,0 +1,17 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
// import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// reportWebVitals();

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,7 @@
/// <reference types="react-scripts" />
declare namespace NodeJS {
interface ProcessEnv {
readonly REACT_APP_OPEN_REPLAY_KEY: string;
}
}

View file

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";

View file

@ -0,0 +1,23 @@
const initialState = {
value: 0,
test: { key: 'test' },
deleteMe: 'a',
arr: [],
}
export function counterReducer(state = initialState, action) {
switch (action.type) {
case 'counter/incremented':
return { ...state, value: state.value + 1 }
case 'counter/decremented':
return { ...state, value: state.value - 1 }
case 'counter/test':
return { ...state, test: { key: 'value1' }, deleteMe: 'asasd'}
case 'couter/test2':
return { ...state, test: { key: 'value2' }, deleteMe: null }
case 'couter/test3':
return { ...state, test: { key: null }, deleteMe: 'aaaaa', arr: [2,2,23,3] }
default:
return state
}
}

View file

@ -0,0 +1,44 @@
import Tracker
, { SanitizeLevel }
from "@openreplay/tracker";
import { createStore, applyMiddleware } from 'redux'
import { counterReducer } from "./store";
import { v4 } from "uuid";
import trackerRedux from '@openreplay/tracker-redux';
export const userId = v4();
localStorage.removeItem("__openreplay_uuid");
localStorage.removeItem("__openreplay_token");
const tracker = new Tracker({
__DISABLE_SECURE_MODE: true,
projectKey: process.env.REACT_APP_KEY!,
ingestPoint: process.env.REACT_APP_INGEST,
// @ts-ignore
network: { capturePayload: true },
verbose: true,
__debug__: true,
onStart: () => {
tracker.setUserID('Testing_bot');
tracker.setMetadata('test', 'cypress')
},
domSanitizer: (node) => {
return node.className ? node.className.includes?.('testhide')
? SanitizeLevel.Hidden : node.className.includes?.('testobscure')
? SanitizeLevel.Obscured : SanitizeLevel.Plain : 0
}
})
const openReplayMiddleware = tracker.use(trackerRedux())
export const store = createStore(
// @ts-ignore
counterReducer,
applyMiddleware(openReplayMiddleware)
)
export const getTracker = () => {
return tracker
}

View file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

File diff suppressed because it is too large Load diff