Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

Commit

Permalink
Fix a bunch of Rollbar bugs in preparation for MOC-19 (#57)
Browse files Browse the repository at this point in the history
* run harlight tests on ci

* add dodom package with initial implementation

* add dodom tests to ci

* add types to dodom in order for it to build correctly

* fix https://app.rollbar.com/a/mocksi/fix/item/mocksi-lite/36

* lint
  • Loading branch information
elg0nz authored Jun 18, 2024
1 parent badbe9b commit a062754
Show file tree
Hide file tree
Showing 20 changed files with 434 additions and 89 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/vitest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Run Vitest

on:
push:
branches:
- main
pull_request:
branches:
- main

permissions:
contents: read

jobs:
build_extension:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Run harlight tests
run: pnpm --prefix packages/harlight exec vitest run
- name: Run dodom tests
run: pnpm --prefix packages/dodom exec vitest run
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ HARlighter is a powerful Chrome extension designed for professionals who need to

### Apps and Packages

- `docs`: Developer Documentation about Owlserver and its components
- `web`: Marketing Home page for Owlserver
- `owlserver-chrome`: Chrome extension for Owlserver
- `mocksi-lite`: Chrome extension for Mocksi
- `@repo/harlight`: Package for building HAR files
- `@repo/dodom`: DOM manipulation package
- `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
- `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
Expand Down
16 changes: 14 additions & 2 deletions apps/mocksi-lite/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,19 @@ chrome.action.onClicked.addListener((activeTab) => {
if (currentTabId && currentTabId < 0) {
return false;
}
console.log("Attaching debugger to tab:", currentTabId);
let activeTabUrl = "";
try {
activeTabUrl = activeTab?.url || "";
} catch (e) {
console.log("Error getting active tab url", e);
activeTabUrl = "";
}

if (activeTabUrl === "" || activeTabUrl.startsWith("chrome://")) {
chrome.action.disable();
return;
}

const version = "1.0";
if (!currentTabId) {
return;
Expand Down Expand Up @@ -160,7 +172,7 @@ function updateDemo(data: Record<string, unknown>) {
}

async function getRecordings() {
const response = await apiCall("recordings");
const response = await apiCall("recordings/");
if (!response || response.length === 0) {
return;
}
Expand Down
4 changes: 2 additions & 2 deletions apps/mocksi-lite/content/RecordButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ export const RecordButton = ({ state, onRecordChange }: RecordButtonProps) => {
RecordingState.READY;

onRecordChange(storageState);
// THIS IS FOR DEMO PURPOSES
const waitTime = 2000; // 2 seconds
if (storageState === RecordingState.ANALYZING) {
setTimeout(() => {
onRecordChange(RecordingState.CREATE);
localStorage.setItem(
MOCKSI_RECORDING_STATE,
RecordingState.CREATE.toString(),
);
}, 3000);
}, waitTime);
}
}, []);

Expand Down
57 changes: 35 additions & 22 deletions apps/mocksi-lite/content/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
RecordingState,
STORAGE_CHANGE_EVENT,
STORAGE_KEY,
SignupURL,
} from "../consts";
import { setRootPosition } from "../utils";
import ContentApp from "./ContentApp";
Expand All @@ -27,28 +28,40 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
root.unmount();
}
root = ReactDOM.createRoot(extensionRoot);
chrome.storage.local.get(STORAGE_KEY).then((value) => {
let parsedData: { email: string } | undefined;
try {
parsedData = JSON.parse(value[STORAGE_KEY]);
} catch (error) {
console.error(error);
MocksiRollbar.error("Error parsing chrome storage");
}
const { email } = parsedData || {};
const recordingState = localStorage.getItem(
MOCKSI_RECORDING_STATE,
) as RecordingState | null;
if (email) {
// we need to initialize recordingState if there's none.
!recordingState &&
localStorage.setItem(MOCKSI_RECORDING_STATE, RecordingState.READY);
}
if (recordingState) {
setRootPosition(recordingState);
}
root.render(<ContentApp isOpen={true} email={email || ""} />);
});
chrome.storage.local
.get(STORAGE_KEY)
.then((value) => {
let parsedData: { email: string } | undefined;
const storedData = value[STORAGE_KEY] || "{}";
try {
parsedData = JSON.parse(storedData);
} catch (error) {
console.log("Error parsing data from storage: ", error);
throw new Error("could not parse data from storage.");
}
if (parsedData === undefined || !parsedData.email) {
throw new Error("No email found in storage.");
}

const { email } = parsedData || {};
const recordingState = localStorage.getItem(
MOCKSI_RECORDING_STATE,
) as RecordingState | null;
if (email) {
// we need to initialize recordingState if there's none.
!recordingState &&
localStorage.setItem(MOCKSI_RECORDING_STATE, RecordingState.READY);
}
if (recordingState) {
setRootPosition(recordingState);
}
root.render(<ContentApp isOpen={true} email={email || ""} />);
})
.catch((error) => {
localStorage.clear();
console.log("Error getting data from storage: ", error);
window.open(SignupURL);
});
}
sendResponse({ status: "success" });
});
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"navigate": "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222 --no-sandbox --incognito",
"capture": "pnpm exec node ./apps/capturer/index.mjs"
"dev": "turbo dev"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
Expand All @@ -18,6 +16,7 @@
"node": ">=18"
},
"dependencies": {
"@repo/harlight": "workspace:*"
"@repo/harlight": "workspace:*",
"@repo/dodom": "workspace:*"
}
}
47 changes: 47 additions & 0 deletions packages/dodom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# DoDomCommander

DoDomCommander is a TypeScript package for command pattern-based DOM manipulation and network mock management. It supports shadow DOM operations, snapshotting, undo/redo functionality, and network mocking.

## Features

- Find nodes by selector
- Clone nodes with auto-generated UUIDs
- Replace text content
- Replace images with data URIs
- Wrap text with overlays
- Manage network mocks
- Snapshot the DOM state
- Undo/Redo operations
- Revert to specific snapshots

## Usage

### Replacing an Image with a Data URI

```typescript
import { ShadowDOMManipulator } from './receivers/ShadowDOMManipulator';
import { ReplaceImageCommand } from './commands/ReplaceImageCommand';

// Example of using the system
const shadowRoot = document.querySelector('#my-shadow-root')?.shadowRoot as ShadowRoot;
const shadowDOMManipulator = new ShadowDOMManipulator(shadowRoot);

console.log('Before replacement:', shadowRoot.innerHTML);
// Output: <img src="https://example.com/old.jpg" alt="Old Image 1">


shadowDOMManipulator.replaceImage('https://example.com/old.jpg', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...=');

console.log('After replacement:', shadowRoot.innerHTML);
// <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...=" alt="Old Image 1" data-mocksi-id="mocksi-1245">

console.log(shadowDOMManipulator.modifiedNodes);
// Output: ["mocksi-1245"]

shadowDOMManipulator.undo();

console.log('After undo:', shadowRoot.innerHTML);
// Output: <img src="https://example.com/old.jpg" alt="Old Image 1">

```

4 changes: 4 additions & 0 deletions packages/dodom/commands/Command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Command {
execute(): void;
undo(): void;
}
Empty file.
26 changes: 26 additions & 0 deletions packages/dodom/commands/ReplaceImageCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Command } from './Command';
import { ShadowDOMManipulator } from '../receivers/ShadowDOMManipulator';

export class ReplaceImageCommand implements Command {
private manipulator: ShadowDOMManipulator;
private oldSrc: string;
private newSrc: string;
private nodeId: string | null;

constructor(manipulator: ShadowDOMManipulator, oldSrc: string, newSrc: string) {
this.manipulator = manipulator;
this.oldSrc = oldSrc;
this.newSrc = newSrc;
this.nodeId = null;
}

execute(): void {
this.nodeId = this.manipulator.replaceImage(this.oldSrc, this.newSrc);
}

undo(): void {
if (this.nodeId) {
this.manipulator.replaceImage(this.newSrc, this.oldSrc, this.nodeId);
}
}
}
Empty file.
28 changes: 28 additions & 0 deletions packages/dodom/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@repo/dodom",
"version": "1.0.0",
"description": "A command pattern-based DOM manipulation and network mock manager package.",
"private": true,
"main": "src/index.ts",
"scripts": {
"build": "tsc",
"lint": "eslint . --max-warnings 0",
"test": "pnpm exec vitest"
},
"devDependencies": {
"@next/eslint-plugin-next": "^14.0.4",
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@types/eslint": "^8.56.1",
"@types/jsdom": "^21.1.7",
"@types/node": "^20.10.6",
"@types/uuid": "^9.0.8",
"eslint": "^8.56.0",
"jsdom": "^24.1.0",
"typescript": "^5.3.3",
"vitest": "^1.6.0"
},
"dependencies": {
"uuid": "^9.0.1"
}
}
41 changes: 41 additions & 0 deletions packages/dodom/receivers/ShadowDOMManipulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { UUIDGenerator } from '../utils/UUIDGenerator';

export class ShadowDOMManipulator {
private shadowRoot: ShadowRoot;
private snapshots: string[] = [];
private modifiedNodes: Map<string, HTMLElement> = new Map();
private uuidGenerator: UUIDGenerator;

constructor(shadowRoot: ShadowRoot, uuidGenerator?: UUIDGenerator) {
this.shadowRoot = shadowRoot;
this.uuidGenerator = uuidGenerator || new UUIDGenerator();
}

replaceImage(oldSrc: string, newSrc: string, nodeId?: string): string {
const img = this.shadowRoot.querySelector(`img[src="${oldSrc}"]`) as HTMLImageElement;
if (img) {
if (!nodeId) {
nodeId = this.uuidGenerator.generate();
img.setAttribute('data-mocksi-id', nodeId);
} else {
img.setAttribute('data-mocksi-id', nodeId);
}
this.snapshots.push(this.shadowRoot.innerHTML);
img.src = newSrc;
this.modifiedNodes.set(nodeId, img);
return nodeId;
}
throw new Error('Image with the specified source not found.');
}

undo(): void {
if (this.snapshots.length > 0) {
const lastSnapshot = this.snapshots.pop()!;
this.shadowRoot.innerHTML = lastSnapshot;
}
}

getModifiedNodes(): string[] {
return Array.from(this.modifiedNodes.keys());
}
}
29 changes: 29 additions & 0 deletions packages/dodom/tests/commands/ReplaceImageCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it, expect } from 'vitest';
import { ShadowDOMManipulator } from '../../receivers/ShadowDOMManipulator';
import { ReplaceImageCommand } from '../../commands/ReplaceImageCommand';
import { UUIDGenerator } from '../../utils/UUIDGenerator';

describe('ReplaceImageCommand', () => {
it('should replace image source and undo the replacement', () => {
const shadowHost = document.createElement('div');
shadowHost.id = 'my-shadow-root';
document.body.appendChild(shadowHost);

const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<img src="https://example.com/old.jpg" alt="Old Image 1">';

const mockUUIDGenerator = {
generate: () => 'mocksi-1234'
} as UUIDGenerator;

const manipulator = new ShadowDOMManipulator(shadowRoot, mockUUIDGenerator);
const command = new ReplaceImageCommand(manipulator, 'https://example.com/old.jpg', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...=');

command.execute();
expect(shadowRoot.innerHTML).toContain('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...=');
expect(shadowRoot.innerHTML).toContain('mocksi-1234');

command.undo();
expect(shadowRoot.innerHTML).toContain('https://example.com/old.jpg');
});
});
Loading

0 comments on commit a062754

Please sign in to comment.