Skip to content

Commit

Permalink
Widget: Create skeleton of graph view (#154)
Browse files Browse the repository at this point in the history
* Center widget on page
* Add widget header, dimensions, and background
* Use Playwright component tests instead of E2E tests, so we can pass in custom props to our component per-test. For now we're using the library @sand4rt/experimental-ct-web to get a mount() function for Lit components, because the Playwright team hasn't yet added native support for Lit components.
  • Loading branch information
andrewedstrom authored Nov 1, 2023
1 parent 67c1cb2 commit 9436112
Show file tree
Hide file tree
Showing 16 changed files with 486 additions and 120 deletions.
2 changes: 1 addition & 1 deletion packages/widget/.prettierrc.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
semi: false,
semi: true,
trailingComma: 'all',
singleQuote: true,
quoteProps: 'as-needed',
Expand Down
25 changes: 12 additions & 13 deletions packages/widget/index.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Lit + TS</title>
<link rel="stylesheet" href="./src/index.css" />
<script type="module" src="/src/docmaps-widget.ts"></script>
</head>
<body>
<docmaps-widget>
</docmaps-widget>
</body>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<link rel='icon' type='image/svg+xml' href='/vite.svg' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>Vite + Lit + TS</title>
<link rel='stylesheet' href='./src/index.css' />
<script type='module' src='/src/docmaps-widget.ts'></script>
</head>
<body>
<docmaps-widget></docmaps-widget>
</body>
</html>
3 changes: 2 additions & 1 deletion packages/widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"preview": "vite preview",
"install:browsers": "playwright install --with-deps",
"test": "playwright test",
"test:server": "vite --port 9005",
"test:ui": "playwright test --ui"
},
"exports": {
Expand All @@ -20,7 +19,9 @@
"lit": "^2.7.6"
},
"devDependencies": {
"@playwright/experimental-ct-core": "^1.39.0",
"@playwright/test": "^1.39.0",
"@sand4rt/experimental-ct-web": "^1.39.0",
"@types/d3": "^7.4.2",
"@types/d3-force": "^3.0.7",
"@types/node": "^18.16.2",
Expand Down
108 changes: 60 additions & 48 deletions packages/widget/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,63 @@
import { defineConfig, devices } from '@playwright/test'
import { defineConfig, devices } from '@sand4rt/experimental-ct-web'
import { widgetConfig } from './vite.config'

const IS_CI = !!process.env.CI

// Locally we only run tests in Chromium
const localBrowsers = [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
]

// In CI, we run tests in all supported browsers
const all_browsers = [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
]

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
snapshotDir: './__snapshots__',
/* Maximum time one test can run for. */
timeout: 20 * 1000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
Expand All @@ -15,58 +67,18 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: IS_CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [['list'], ['html']],
reporter: IS_CI ? [['list'], ['html']] : 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:9005',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
/* Port to use for Playwright component endpoint. */
ctPort: 3100,

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

// /* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm run test:server',
url: 'http://localhost:9005',
reuseExistingServer: !IS_CI,
ctViteConfig: widgetConfig,
},

/* Configure projects for major browsers */
projects: IS_CI ? all_browsers : localBrowsers,
})
12 changes: 12 additions & 0 deletions packages/widget/playwright/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Testing Page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions packages/widget/playwright/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Import styles, initialize component theme here.
// import '../src/common.css';
11 changes: 11 additions & 0 deletions packages/widget/src/assets/logo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { svg } from 'lit'

export const logo = svg`
<svg class="docmap-logo" width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path fill-rule='evenodd' clip-rule='evenodd' d='M3.78445 0.521739H1.28598C0.859842 0.521739 0.514391 0.872125 0.514391 1.30435V3.83851C0.514391 4.27073 0.859842 4.62112 1.28598 4.62112H3.78445C4.21058 4.62112 4.55603 4.27073 4.55603 3.83851V1.30435C4.55603 0.872125 4.21058 0.521739 3.78445 0.521739ZM1.28598 0C0.575752 0 0 0.583976 0 1.30435V3.83851C0 4.55888 0.575751 5.14286 1.28598 5.14286H3.78445C4.49467 5.14286 5.07043 4.55888 5.07043 3.83851V1.30435C5.07043 0.583977 4.49467 0 3.78445 0H1.28598Z' fill='white'/>
<path d='M0 9.42857C0 8.00841 1.13505 6.85714 2.53521 6.85714C3.93537 6.85714 5.07043 8.00841 5.07043 9.42857C5.07043 10.8487 3.93537 12 2.53521 12C1.13505 12 0 10.8487 0 9.42857Z' fill='white'/>
<path d='M6.92957 8.16149C6.92957 7.44112 7.50533 6.85714 8.21555 6.85714H10.714C11.4242 6.85714 12 7.44112 12 8.16149V10.6957C12 11.416 11.4242 12 10.714 12H8.21555C7.50533 12 6.92957 11.416 6.92957 10.6957V8.16149Z' fill='white'/>
<path d='M0 9.42857C0 8.00841 1.13505 6.85714 2.53521 6.85714C3.93537 6.85714 5.07043 8.00841 5.07043 9.42857C5.07043 10.8487 3.93537 12 2.53521 12C1.13505 12 0 10.8487 0 9.42857Z' fill='white'/>
<path d='M6.92957 8.16149C6.92957 7.44112 7.50533 6.85714 8.21555 6.85714H10.714C11.4242 6.85714 12 7.44112 12 8.16149V10.6957C12 11.416 11.4242 12 10.714 12H8.21555C7.50533 12 6.92957 11.416 6.92957 10.6957V8.16149Z' fill='white'/>
</svg>
`
38 changes: 24 additions & 14 deletions packages/widget/src/docmaps-widget.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { html, LitElement } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { customCss } from './styles'
import { logo } from './assets/logo'
import * as d3 from 'd3'
import { SimulationLinkDatum } from 'd3'
import { SimulationNodeDatum } from 'd3-force'

type Node = SimulationNodeDatum & { id: string }
type Link = SimulationLinkDatum<Node>

const CANVAS_WIDTH: number = 500
const CANVAS_HEIGHT: number = 300
const CANVAS_ID: string = 'd3-canvas'
const WIDGET_SIZE: number = 500
const GRAPH_CANVAS_HEIGHT: number = 375
const GRAPH_CANVAS_ID: string = 'd3-canvas'
const NODE_RADIUS: number = 20

@customElement('docmaps-widget')
export class DocmapsWidget extends LitElement {
@property({ type: String })
doi: string = ''

@property({ type: Number })
count = 3
count: number = 3

nodes: Node[] = [
{ id: 'N1', x: 100, y: 150 },
Expand Down Expand Up @@ -59,17 +63,17 @@ export class DocmapsWidget extends LitElement {
return
}

d3.select(this.shadowRoot.querySelector(`#${CANVAS_ID} svg`)).remove()
const canvas = this.shadowRoot.querySelector(`#${CANVAS_ID}`)
d3.select(this.shadowRoot.querySelector(`#${GRAPH_CANVAS_ID} svg`)).remove()
const canvas = this.shadowRoot.querySelector(`#${GRAPH_CANVAS_ID}`)
if (!canvas) {
throw new Error('SVG element not found')
}

const svg = d3
.select(canvas)
.append('svg')
.attr('width', CANVAS_WIDTH)
.attr('height', CANVAS_HEIGHT)
.attr('width', WIDGET_SIZE)
.attr('height', GRAPH_CANVAS_HEIGHT)

// Make copy of Nodes and Links because d3 mutates whatever Nodes/Links we pass it
const displayNodes: Node[] = JSON.parse(JSON.stringify(this.nodes))
Expand All @@ -89,8 +93,8 @@ export class DocmapsWidget extends LitElement {
.force(
'center',
d3.forceCenter(
Math.floor(CANVAS_WIDTH / 2),
Math.floor(CANVAS_HEIGHT / 2),
Math.floor(WIDGET_SIZE / 2),
Math.floor(GRAPH_CANVAS_HEIGHT / 2),
),
)

Expand Down Expand Up @@ -146,12 +150,18 @@ export class DocmapsWidget extends LitElement {

render() {
return html`
<h1>Docmaps</h1>
<div id='${CANVAS_ID}'
style='display: block; border-style: groove; width: ${CANVAS_WIDTH}; height: ${CANVAS_HEIGHT}'>
<div class='widget-header'>
${logo}
<span>DOCMAP</span>
</div>
<h2>${this.doi}</h2>
<div
id='${GRAPH_CANVAS_ID}'
style='display: block; width: ${WIDGET_SIZE}; height: ${GRAPH_CANVAS_HEIGHT}'
></div>
<div class='card'>
<button @click='${this._onClick}' part='button'>
Add ${this.count + 1}th node
Expand Down
4 changes: 2 additions & 2 deletions packages/widget/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ a:hover {
body {
margin: 0;
display: flex;
place-items: center;
align-items: center;
justify-content: center;
min-width: 320px;
min-height: 100vh;
}

31 changes: 27 additions & 4 deletions packages/widget/src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {css, CSSResult} from "lit";
// These are the styles used within the lit component
export const customCss: CSSResult = css`
:host {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
width: 500px;
height: 500px;
background: #EDEDED;
border: 1px solid
}
.logo {
Expand Down Expand Up @@ -65,4 +65,27 @@ export const customCss: CSSResult = css`
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.widget-header {
width: 500px;
height: 25px;
background: #043945;
display: flex;
align-items: center;
}
.widget-header span {
color: white;
font-size: 12px;
font-family: 'SF Pro Display', 'Helvetica Neue', Arial, sans-serif;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 2.40px;
word-wrap: break-word;
}
.docmap-logo {
display: inline-block;
margin: 7px 13px 6px 11px
}
`
36 changes: 36 additions & 0 deletions packages/widget/tests/docmap-widget.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expect, MountOptions, test } from '@sand4rt/experimental-ct-web'
import { DocmapsWidget } from '../src'
import { Locator } from '@playwright/test'
import { JsonObject } from '@playwright/experimental-ct-core/types/component'

const options: MountOptions<JsonObject, DocmapsWidget> = {
props: { doi: 'test-doi' },
}

const dois = ['doi-1', 'doi-2']
for (const doi of dois) {
test(`It renders the DOI: ${doi}`, async ({ mount }) => {
const widget: Locator = await mount(DocmapsWidget, { props: { doi } })
await expect(widget).toContainText(doi)
})
}

test('The header bar is displayed in the graph view', async ({ mount }) => {
const widget: Locator = await mount(DocmapsWidget, options)
await expect(widget.locator('.widget-header')).toContainText('DOCMAP')
})

test('Clicking button increments the count', async ({ mount }) => {
const widget: Locator = await mount(DocmapsWidget, options)
await expect(widget.locator('circle')).toHaveCount(3)
await expect(
widget.getByRole('button', { name: 'Add 4th Node' }),
).toBeVisible()

await widget.getByRole('button', { name: 'Add 4th Node' }).click()

await expect(
widget.getByRole('button', { name: 'Add 5th Node' }),
).toBeVisible()
await expect(widget.locator('circle')).toHaveCount(4)
})
Loading

0 comments on commit 9436112

Please sign in to comment.