-
Notifications
You must be signed in to change notification settings - Fork 31
test: add contract test boilerplate to react universal #1074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| next-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # React SDK contract-tests | ||
|
|
||
| This directory contains the contract test implementation for the LaunchDarkly React SDK using the [SDK Test Harness](https://github.com/launchdarkly/sdk-test-harness). | ||
|
|
||
| ## Architecture | ||
| > NOTE: much of the test architecture is based off of | ||
| > [browser contract test](../../browser/contract-tests). | ||
|
|
||
| This contract test consists of 3 components: | ||
|
|
||
| 1. [Adapter](../../browser/contract-tests/adapter/): A Node.js server that: | ||
| - Exposes a REST API on port 8000 for the test harness | ||
| - Runs a WebSocket server on port 8001 for browser communication | ||
| - Translates REST commands to WebSocket messages | ||
|
|
||
| 2. Entity: A browser application (NextJS app) that: | ||
| - Connects to the adapter via WebSocket | ||
| - Implements the actual SDK test logic | ||
| - Runs the React SDK in a real browser environment | ||
|
|
||
| 3. [Test harness](https://github.com/launchdarkly/sdk-test-harness): The SDK test harness that: | ||
| - Sends test commands via REST API to the adapter (port 8000) | ||
| - Validates SDK behavior across different scenarios | ||
|
|
||
| ## Running Locally | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - Node.js 18 or later | ||
| - Yarn | ||
| - A modern browser (for manual testing) | ||
|
|
||
| ### Quick Start | ||
|
|
||
| ```bash | ||
| # Install the workspace if you haven't already | ||
| yarn install | ||
|
|
||
| # Build contract tests and browser contract test (dependency) | ||
| yarn workspaces foreach -pR --topological-dev --from 'browser-contract-test-adapter' run build | ||
| yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/react-sdk-contract-tests' run build | ||
| ``` | ||
|
|
||
| From the repository root | ||
| ```bash | ||
| ./packages/sdk/browser/contract-tests/run-test-service.sh | ||
| ``` | ||
|
|
||
| This script will: | ||
| 1. Start the adapter (WebSocket bridge) | ||
| 2. Start the app | ||
|
|
||
| The services will be available at: | ||
| - Adapter REST API: http://localhost:8000 | ||
| - Adapter WebSocket: ws://localhost:8001 | ||
| - Browser App: http://localhost:8002 | ||
|
|
||
| You then run the `sdk-test-harness`. More information is available here: https://github.com/launchdarkly/sdk-test-harness | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| 'use client'; | ||
|
|
||
| import React, { useEffect } from 'react'; | ||
|
|
||
| import AdaptorWebSocket from './websocket'; | ||
|
|
||
| const ws = new AdaptorWebSocket('ws://localhost:8001'); | ||
|
|
||
| export default function App() { | ||
| useEffect(() => { | ||
| ws.connect(); | ||
| return () => { | ||
| ws.disconnect(); | ||
| }; | ||
| }, []); | ||
|
|
||
| return ( | ||
| <html lang="en"> | ||
| <body> | ||
| <div> Hello test harness </div> | ||
| </body> | ||
| </html> | ||
| ); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Next.js layout missing required children propMedium Severity The |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| // eslint-disable no-console | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as https://github.com/launchdarkly/js-core/blob/main/packages/sdk/browser/contract-tests/entity/src/TestHarnessWebSocket.ts but with no client implementation yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Invalid eslint directive syntax does nothingLow Severity The comment |
||
|
|
||
| export default class AdaptorWebSocket { | ||
| private _ws?: WebSocket; | ||
|
|
||
| constructor(private readonly _url: string) {} | ||
|
|
||
| connect() { | ||
| console.log(`Connecting to web socket.`); | ||
| this._ws = new WebSocket(this._url, ['v1']); | ||
| this._ws.onopen = () => { | ||
| console.log('Connected to websocket.'); | ||
| }; | ||
| this._ws.onclose = () => { | ||
| console.log('Websocket closed. Attempting to reconnect in 1 second.'); | ||
| setTimeout(() => { | ||
| this.connect(); | ||
| }, 1000); | ||
| }; | ||
| this._ws.onerror = (err) => { | ||
| console.log(`error:`, err); | ||
| }; | ||
|
|
||
| this._ws.onmessage = async (msg) => { | ||
| console.log('Test harness message', msg); | ||
| const data = JSON.parse(msg.data); | ||
| const resData: any = { reqId: data.reqId }; | ||
| // TODO: currently copied from the browser contract tests | ||
| // will need to figure out what the actual capabilities are. | ||
| switch (data.command) { | ||
| case 'getCapabilities': | ||
| resData.capabilities = [ | ||
| 'client-side', | ||
| 'service-endpoints', | ||
| 'tags', | ||
| 'user-type', | ||
| 'inline-context-all', | ||
| 'anonymous-redaction', | ||
| 'strongly-typed', | ||
| 'client-prereq-events', | ||
| 'client-per-context-summaries', | ||
| 'track-hooks', | ||
| ]; | ||
|
|
||
| break; | ||
| case 'createClient': | ||
| case 'runCommand': | ||
| case 'deleteClient': | ||
| default: | ||
| break; | ||
| } | ||
|
|
||
| this.send(resData); | ||
| }; | ||
| } | ||
|
|
||
| disconnect() { | ||
| this._ws?.close(); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disconnect triggers reconnection instead of closing cleanlyMedium Severity The Additional Locations (1) |
||
|
|
||
| send(data: unknown) { | ||
| this._ws?.send(JSON.stringify(data)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import type { NextConfig } from 'next'; | ||
|
|
||
| const nextConfig: NextConfig = { | ||
| /* config options here */ | ||
| }; | ||
|
|
||
| export default nextConfig; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #!/usr/bin/env node | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| /** | ||
| * Opens a headless browser and navigates to the contract test entity page. | ||
| * Keeps the browser open until the process is terminated. | ||
| * | ||
| * Usage: node open-browser.mjs [url] | ||
| * Default URL: http://localhost:8002 | ||
| */ | ||
|
|
||
| import { chromium } from 'playwright'; | ||
|
|
||
| const url = process.argv[2] || 'http://localhost:8002'; | ||
|
|
||
| console.log(`Opening headless browser at ${url}...`); | ||
|
|
||
| const browser = await chromium.launch({ | ||
| headless: true, | ||
| args: ['--no-sandbox', '--disable-setuid-sandbox'] | ||
| }); | ||
|
|
||
| const context = await browser.newContext(); | ||
| const page = await context.newPage(); | ||
|
|
||
| // Log console messages from the browser | ||
| page.on('console', (msg) => { | ||
| console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); | ||
| }); | ||
|
|
||
| // Log page errors | ||
| page.on('pageerror', (error) => { | ||
| console.error(`[Browser Error] ${error.message}`); | ||
| }); | ||
|
|
||
| await page.goto(url); | ||
|
|
||
| console.log('Browser is open and running. Press Ctrl+C to close.'); | ||
|
|
||
| // Keep the process alive | ||
| await new Promise(() => { | ||
| // Intentionally never resolve - keeps browser open until process is killed | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "name": "@launchdarkly/react-sdk-contract-tests", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "packageManager": "yarn@3.4.1", | ||
| "scripts": { | ||
| "install-playwright-browsers": "playwright install --with-deps chromium", | ||
| "start:adapter": "yarn workspace browser-contract-test-adapter run start", | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NOTE: I am using the adapter directly from the browser contract test... I think this is okay, probably should work on extracting this ws bridge to a common private module |
||
| "dev": "next dev -p 8002", | ||
| "build": "next build", | ||
| "start:entity": "next start -p 8002", | ||
| "prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)' --ignore-path ../../../../.prettierignore" | ||
| }, | ||
| "dependencies": { | ||
| "next": "16.1.4", | ||
| "react": "19.2.3", | ||
| "react-dom": "19.2.3" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^20", | ||
| "@types/react": "^19", | ||
| "@types/react-dom": "^19", | ||
| "eslint": "^8.45.0", | ||
| "eslint-config-airbnb-base": "^15.0.0", | ||
| "eslint-config-airbnb-typescript": "^17.1.0", | ||
| "eslint-config-prettier": "^8.8.0", | ||
| "eslint-plugin-import": "^2.27.5", | ||
| "eslint-plugin-jest": "^27.6.3", | ||
| "eslint-plugin-prettier": "^5.0.0", | ||
| "playwright": "^1.49.1", | ||
| "prettier": "^3.0.0", | ||
| "typescript": "^5" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| yarn workspace @launchdarkly/react-sdk-contract-tests run start:adapter & yarn workspace @launchdarkly/react-sdk-contract-tests run start:entity && kill $! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2017", | ||
| "lib": ["dom", "dom.iterable", "esnext"], | ||
| "allowJs": true, | ||
| "skipLibCheck": true, | ||
| "strict": true, | ||
| "noEmit": true, | ||
| "esModuleInterop": true, | ||
| "module": "esnext", | ||
| "moduleResolution": "bundler", | ||
| "resolveJsonModule": true, | ||
| "isolatedModules": true, | ||
| "jsx": "react-jsx", | ||
| "incremental": true, | ||
| "plugins": [ | ||
| { | ||
| "name": "next" | ||
| } | ||
| ], | ||
| "paths": { | ||
| "@/*": ["./*"] | ||
| } | ||
| }, | ||
| "include": [ | ||
| "next-env.d.ts", | ||
| "**/*.ts", | ||
| "**/*.tsx", | ||
| ".next/types/**/*.ts", | ||
| ".next/dev/types/**/*.ts", | ||
| "**/*.mts" | ||
| ], | ||
| "exclude": ["node_modules"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stubbed for now