├── jest-setup.js ├── .vscode └── extensions.json ├── assets ├── tabs.gif ├── propchanges.gif └── treediagram.gif ├── src ├── test │ ├── Main │ │ ├── main.ts │ │ └── main.test.ts │ └── App │ │ ├── App.svelte │ │ └── App.test.js ├── vite-env.d.ts ├── lib │ ├── containers │ │ ├── refresh-svgrepo-com.png │ │ ├── Tabs.svelte │ │ ├── SingleTab.svelte │ │ └── TabAdder.svelte │ └── components │ │ ├── SplitpaneContainer.svelte │ │ ├── Editor │ │ ├── Editable.svelte │ │ ├── Props.svelte │ │ └── Expandable.svelte │ │ ├── Editor.svelte │ │ └── Tree.svelte ├── entry.ts ├── stores │ └── Store.js ├── app.css └── routes │ └── +layout.svelte ├── static ├── DevtoolsPanel │ ├── devtools.html │ └── devtools.js ├── icons │ └── sveltescope-logo.png ├── Popup │ ├── popup.ts │ ├── popup.html │ ├── popup.css │ └── Popup.svelte ├── setupTests.ts ├── ContentScripts │ ├── svelte-listener │ │ ├── index.js │ │ ├── listener.js │ │ ├── profiler.js │ │ └── svelte.js │ ├── contentScriptIsolate.js │ └── contentScript.js ├── Background │ └── background.js └── manifest.json ├── tsconfig.node.json ├── svelte.config.js ├── index.html ├── jest ├── .gitignore ├── vite.config.ts ├── jest.config.cjs ├── tsconfig.json ├── rollup.config.js ├── package.json ├── __mocks__ ├── chrome.ts └── mockData.ts └── README.md /jest-setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/tabs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/SvelteScope/HEAD/assets/tabs.gif -------------------------------------------------------------------------------- /src/test/Main/main.ts: -------------------------------------------------------------------------------- 1 | export function isPositive (n: number){ 2 | return n > 0; 3 | } -------------------------------------------------------------------------------- /assets/propchanges.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/SvelteScope/HEAD/assets/propchanges.gif -------------------------------------------------------------------------------- /assets/treediagram.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/SvelteScope/HEAD/assets/treediagram.gif -------------------------------------------------------------------------------- /static/DevtoolsPanel/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /static/icons/sveltescope-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/SvelteScope/HEAD/static/icons/sveltescope-logo.png -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /src/lib/containers/refresh-svgrepo-com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/SvelteScope/HEAD/src/lib/containers/refresh-svgrepo-com.png -------------------------------------------------------------------------------- /static/Popup/popup.ts: -------------------------------------------------------------------------------- 1 | import Popup from './Popup.svelte'; 2 | 3 | export default new Popup({ 4 | target: document.getElementById('popup'), 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /src/entry.ts: -------------------------------------------------------------------------------- 1 | import './app.css'; 2 | import App from './routes/+layout.svelte'; 3 | 4 | export default new App({ 5 | target: document.getElementById('app')!, 6 | }); -------------------------------------------------------------------------------- /static/DevtoolsPanel/devtools.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create( 2 | 'SvelteScope: Svelte Debugging Tool', 3 | 'icons/sveltescope-logo.png', 4 | 'index.html' 5 | ); 6 | -------------------------------------------------------------------------------- /src/test/App/App.svelte: -------------------------------------------------------------------------------- 1 | 4 |
{message}
5 |
6 | -------------------------------------------------------------------------------- /static/setupTests.ts: -------------------------------------------------------------------------------- 1 | // import mockChrome, { MockChrome } from './../__mocks__/chrome'; 2 | // import '@testing-library/jest-dom'; 3 | 4 | // declare global { 5 | // var chrome: MockChrome 6 | // } 7 | // global.chrome = mockChrome; 8 | 9 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "strict": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /static/ContentScripts/svelte-listener/index.js: -------------------------------------------------------------------------------- 1 | export { addNodeListener, removeNodeListener } from './listener.js' 2 | export { getNode, getAllNodes, getRootNodes, getSvelteVersion } from './svelte.js' 3 | export { stopProfiler, startProfiler, clearProfiler } from './profiler.js' -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /jest: -------------------------------------------------------------------------------- 1 | // jest.config.js 2 | export default { 3 | transform: { 4 | '^.+\\.ts$': 'ts-jest', 5 | '^.+\\.svelte$': [ 6 | 'svelte-jester', 7 | { 8 | preprocess: true, 9 | }, 10 | ], 11 | }, 12 | moduleFileExtensions: ['js', 'ts', 'svelte'], 13 | setupFiles: ['/test/mock-extension-apis.js'] 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | build 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /static/Popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /static/Popup/popup.css: -------------------------------------------------------------------------------- 1 | body { 2 | border: solid black 1px; 3 | border-radius: 5px; 4 | padding: 5px 10px; 5 | width: 220px; 6 | background-color: orange; 7 | color: #fff; 8 | font-size: 12px; 9 | line-height: 24px; 10 | } 11 | 12 | span{ 13 | color: black; 14 | font-weight: bold; 15 | } 16 | 17 | .name{ 18 | font-weight: bold; 19 | } 20 | -------------------------------------------------------------------------------- /static/Background/background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onMessage.addListener('readScripts', (details) => { 2 | console.log('logging details from readScript message: ', details); 3 | }); 4 | 5 | chrome.scripting.registerContentScripts([ 6 | { 7 | id: 'inpage', 8 | matches: ['http://*/*', 'https://*/*'], 9 | js: ['ContentScripts/contentScript.js'], 10 | runAt: 'document_start', 11 | world: 'MAIN', 12 | }, 13 | ]); 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/stores/Store.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | //store for components 4 | export const RootComponentStore = writable({}); 5 | 6 | //store for svelte version 7 | export const SvelteVersionStore = writable(); 8 | 9 | //Selected Node Store 10 | export const SelectedNodeAttributes = writable({}); 11 | 12 | export const CurrentTabStore = writable({ 13 | currentTab: 1 14 | }); 15 | export const SnapshotStore = writable({}) 16 | export const DefaultSnapShotStore = writable({}) 17 | 18 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { resolve } from 'node:path'; 3 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig(() => { 7 | return { 8 | plugins: [ 9 | svelte() 10 | ], 11 | build: { 12 | outDir: 'build', 13 | }, 14 | publicDir: 'static', 15 | resolve: { 16 | alias: { 17 | $lib: resolve(__dirname, 'src/lib'), 18 | }, 19 | }, 20 | }; 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest/presets/default-esm', 4 | testEnvironment: 'node', 5 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', 'svelte'], 6 | transform: { 7 | '^.+\\.(ts|tsx)$': 'ts-jest', 8 | "^.+\\.svelte$": "svelte-jester" 9 | }, 10 | testMatch: ['**/*.test.(ts|tsx|js|jsx)'], 11 | extensionsToTreatAsEsm: [".svelte"], 12 | testEnvironment: "jsdom", 13 | setupFilesAfterEnv: ["/jest-setup.js"] 14 | }; -------------------------------------------------------------------------------- /src/test/Main/main.test.ts: -------------------------------------------------------------------------------- 1 | import { isPositive } from "./main"; 2 | 3 | 4 | 5 | describe('isPositive', () => { 6 | it('should return true when n > 0', () => { 7 | expect(isPositive(1)).toBe(true) 8 | expect(isPositive(1)).toBe(true) 9 | expect(isPositive(1)).toBe(true) 10 | }) 11 | 12 | it('should return false when n = 0', () => { 13 | expect(isPositive(0)).toBe(false) 14 | }) 15 | 16 | it('should return false when n < 0', () => { 17 | expect(isPositive(-5)).toBe(false) 18 | }) 19 | }) -------------------------------------------------------------------------------- /src/lib/containers/Tabs.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /static/ContentScripts/svelte-listener/listener.js: -------------------------------------------------------------------------------- 1 | const listenerList = [] 2 | export function addNodeListener(listener) { 3 | listenerList.push(listener) 4 | } 5 | 6 | export function removeNodeListener(listener) { 7 | const index = listenerList.indexOf(listener) 8 | if (index == -1) return false 9 | 10 | listenerList.splice(index, 1) 11 | return true 12 | } 13 | 14 | export function add(node, anchorNode) { 15 | for (const listener of listenerList) listener.add(node, anchorNode) 16 | } 17 | 18 | export function update(node) { 19 | if (!node) return 20 | 21 | for (const listener of listenerList) listener.update(node) 22 | } 23 | 24 | export function remove(node) { 25 | for (const listener of listenerList) listener.remove(node) 26 | } 27 | 28 | export function profile(frame) { 29 | for (const listener of listenerList) listener.profile(frame) 30 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "verbatimModuleSyntax": false, 12 | /** 13 | * Typecheck JS in `.svelte` and `.js` files by default. 14 | * Disable checkJs if you'd like to use dynamic types in JS. 15 | * Note that setting allowJs false does not prevent the use 16 | * of JS in `.svelte` files. 17 | */ 18 | "allowJs": true, 19 | "checkJs": true, 20 | "isolatedModules": true 21 | }, 22 | "include": [ 23 | "src/**/*.ts", 24 | "src/**/*.js", 25 | "src/**/*.svelte", 26 | "static/Popup/popup.ts", "src/test/App/App.test.ts", 27 | 28 | ], 29 | "exclude": ["./build/**/*"], 30 | "references": [{ "path": "./tsconfig.node.json" }] 31 | } 32 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import { 4 | chromeExtension, 5 | simpleReloader, 6 | } from 'rollup-plugin-chrome-extension'; 7 | import svelte from 'rollup-plugin-svelte'; 8 | import css from 'rollup-plugin-css-only' 9 | 10 | export default { 11 | input: 'static/manifest.json', 12 | output: { 13 | dir: 'build', 14 | format: 'esm', 15 | // sourcemap: true, 16 | }, 17 | plugins: [ 18 | // always put chromeExtension() before other plugins 19 | chromeExtension({ 20 | preloadEntries: [ 21 | 'static/contentScript.js', 22 | 'static/contentScriptIsolate.js', 23 | // Include other entry points as needed), 24 | ], 25 | }), 26 | simpleReloader(), 27 | // the plugins below are optional 28 | resolve(), 29 | commonjs(), 30 | svelte(), 31 | css({ 32 | output:{ 33 | dir: 'build', 34 | } 35 | }) 36 | ], 37 | }; 38 | -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "SvelteScope", 4 | "version": "1.0.0", 5 | "icons": { 6 | "16": "icons/sveltescope-logo.png", 7 | "48": "icons/sveltescope-logo.png", 8 | "128": "icons/sveltescope-logo.png" 9 | }, 10 | "permissions": [ 11 | "tabs", 12 | "activeTab", 13 | "scripting" 14 | ], 15 | "devtools_page": "DevtoolsPanel/devtools.html", 16 | "background": { 17 | "service_worker": "Background/background.js", 18 | "type": "module", 19 | "minimum_chrome_version": "92" 20 | }, 21 | "content_scripts": [ 22 | { 23 | "matches": ["http://*/*", "https://*/*", ""], 24 | "js": ["ContentScripts/contentScript.js"], 25 | "run_at": "document_start" 26 | }, 27 | { 28 | "matches": ["http://*/*", "https://*/*", ""], 29 | "js": ["ContentScripts/contentScriptIsolate.js"], 30 | "run_at": "document_start" 31 | } 32 | ], 33 | "action": { 34 | "default_popup": "Popup/popup.html" 35 | }, 36 | "host_permissions": [""] 37 | } -------------------------------------------------------------------------------- /src/lib/containers/SingleTab.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |
37 | {#if currentData} 38 | 39 | {/if} 40 |
41 | -------------------------------------------------------------------------------- /static/ContentScripts/svelte-listener/profiler.js: -------------------------------------------------------------------------------- 1 | import { profile } from './listener.js' 2 | 3 | let topFrame = {} 4 | let currentFrame = topFrame 5 | let profilerEnabled = false 6 | 7 | export function startProfiler() { 8 | topFrame = { 9 | type: 'top', 10 | start: performance.now(), 11 | children: [], 12 | } 13 | currentFrame = topFrame 14 | profilerEnabled = true 15 | } 16 | 17 | export function stopProfiler() { 18 | topFrame.end = performance.now(), 19 | profilerEnabled = false 20 | } 21 | 22 | export function clearProfiler() { 23 | topFrame.children = [] 24 | topFrame.start = performance.now() 25 | } 26 | 27 | export function updateProfile(node, type, fn, ...args) { 28 | if (!profilerEnabled) { 29 | fn(...args) 30 | return 31 | } 32 | 33 | const parentFrame = currentFrame 34 | currentFrame = { 35 | type, 36 | node: node.id, 37 | start: performance.now(), 38 | children: [], 39 | } 40 | parentFrame.children.push(currentFrame) 41 | fn(...args) 42 | currentFrame.end = performance.now() 43 | currentFrame.duration = currentFrame.end - currentFrame.start 44 | currentFrame = parentFrame 45 | 46 | if (currentFrame.type == 'top') 47 | topFrame.duration = topFrame.children[topFrame.children.length - 1].end - topFrame.children[0].start 48 | 49 | profile(topFrame) 50 | } -------------------------------------------------------------------------------- /src/lib/components/SplitpaneContainer.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 |
17 |

18 | Svelte Version: {svelteVersion} 19 |

20 | 21 |
22 |
23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 | 71 | -------------------------------------------------------------------------------- /static/Popup/Popup.svelte: -------------------------------------------------------------------------------- 1 | 39 | 40 |
41 | 42 | {#if version} 43 |

This webpage is using Svelte

44 |

Svelte version: {version}

45 | {:else} 46 |

This webpage is NOT using Svelte

47 |

48 | SvelteScope cannot inject this page 49 |

50 | {/if} 51 |
52 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | } 5 | 6 | :root { 7 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 8 | line-height: 1.5; 9 | font-weight: 400; 10 | 11 | color-scheme: light dark; 12 | color: rgba(255, 255, 255, 0.87); 13 | background-color: #242424; 14 | 15 | font-synthesis: none; 16 | text-rendering: optimizeLegibility; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | } 20 | 21 | a { 22 | font-weight: 500; 23 | color: #646cff; 24 | text-decoration: inherit; 25 | } 26 | a:hover { 27 | color: #535bf2; 28 | } 29 | 30 | body { 31 | margin: 0; 32 | display: flex; 33 | place-items: center; 34 | width: 100vw; 35 | height: 100vh; 36 | /* min-width: 320px; 37 | min-height: 100vh; */ 38 | } 39 | 40 | h1 { 41 | font-size: 3.2em; 42 | line-height: 1.1; 43 | } 44 | 45 | .card { 46 | padding: 2em; 47 | } 48 | 49 | #app { 50 | width: 100%; 51 | height: 100%; 52 | /* max-width: 1280px; */ 53 | /* margin: 0 auto; */ 54 | /* padding: 2rem; */ 55 | /* text-align: center; */ 56 | } 57 | 58 | button { 59 | border-radius: 8px; 60 | border: 1px solid transparent; 61 | padding: 0.6em 1.2em; 62 | font-size: 1em; 63 | font-weight: 500; 64 | font-family: inherit; 65 | background-color: #1a1a1a; 66 | cursor: pointer; 67 | transition: border-color 0.25s; 68 | } 69 | button:hover { 70 | border-color: #646cff; 71 | } 72 | button:focus, 73 | button:focus-visible { 74 | outline: 4px auto -webkit-focus-ring-color; 75 | } 76 | 77 | @media (prefers-color-scheme: light) { 78 | :root { 79 | color: #213547; 80 | background-color: #ffffff; 81 | } 82 | a:hover { 83 | color: #747bff; 84 | } 85 | button { 86 | background-color: #f9f9f9; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/App/App.test.js: -------------------------------------------------------------------------------- 1 | // import {fireEvent, render} from '@testing-library/svelte'; 2 | 3 | // import App from './App.svelte'; 4 | 5 | // describe('App', () => { 6 | // test('message and input have alwayys same values', async () => { 7 | // const {container, getByText} = render(App); 8 | // const input = container.querySelector('input'); 9 | // const button = container.querySelector('button'); 10 | 11 | // // initial state 12 | // expect(getByText('hi')); 13 | // expect(input.value).toBe('hi'); 14 | 15 | // // // type in input 16 | // // await fireEvent.input(input, {target: {value: 'yum yum'}}); 17 | // // expect(getByText('yum yum')); 18 | // // expect(input.value).toBe('yum yum'); 19 | // // // click button 20 | // // await fireEvent.click(button); 21 | // // expect(getByText('bye')); 22 | // // expect(input.value).toBe('bye'); 23 | // }); 24 | // }); 25 | 26 | 27 | 28 | 29 | import { fireEvent, render} from '@testing-library/svelte'; 30 | import App from './App.svelte'; 31 | 32 | describe('App', () => { 33 | test('message and input have always the same values', async () => { 34 | const { container, getByText } = render(App); 35 | const input = container.querySelector('input'); 36 | const button = container.querySelector('button'); 37 | 38 | // Perform null checks before using the variables 39 | if (input !== null && button !== null) { 40 | // initial state 41 | expect(getByText('hi')); 42 | expect(input.value).toBe('hi'); 43 | // type in input 44 | await fireEvent.input(input, { target: { value: 'yum yum' } }); 45 | expect(getByText('yum yum')); 46 | expect(input.value).toBe('yum yum'); 47 | // click button 48 | await fireEvent.click(button); 49 | expect(getByText('bye')); 50 | expect(input.value).toBe('bye'); 51 | } else { 52 | // Handle case when input or button is not found 53 | throw new Error('Input or button not found'); 54 | } 55 | }); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /src/lib/components/Editor/Editable.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | {#if editing} 13 | { 16 | editing = false; 17 | // @ts-expect-error - target and value exists 18 | const updated = target.value; 19 | value = JSON.parse(updated); 20 | dispatch('change', updated); 21 | }} 22 | on:keydown={({ key, target }) => { 23 | if (key !== 'Enter') return; 24 | editing = false; 25 | // @ts-expect-error - target and value exists 26 | const updated = target.value; 27 | value = JSON.parse(updated); 28 | dispatch('change', updated); 29 | }} 30 | /> 31 | {:else} 32 | 33 | 34 | (editing = !readonly)}> 35 | {JSON.stringify(value)} 36 | 37 | {/if} 38 | 39 | 85 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 73 | 74 |
75 | 76 |
77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-component-explorer", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "NODE_ENV=development vite build --watch", 8 | "build": "vite build && rollup -c", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.json", 11 | "test": "npx --node-options=\"--experimental-vm-modules\" jest src", 12 | "test:watch": "npx --node-options=\"--experimental-vm-modules\" jest src --watch" 13 | }, 14 | "devDependencies": { 15 | "@babel/preset-env": "^7.24.3", 16 | "@babel/preset-typescript": "^7.24.1", 17 | "@iconify/svelte": "^3.1.6", 18 | "@rollup/plugin-commonjs": "^25.0.7", 19 | "@rollup/plugin-node-resolve": "^15.2.3", 20 | "@sveltejs/vite-plugin-svelte": "^3.0.2", 21 | "@testing-library/jest-dom": "^6.4.2", 22 | "@testing-library/svelte": "^4.1.0", 23 | "@tsconfig/svelte": "^5.0.2", 24 | "@types/chrome": "^0.0.263", 25 | "@types/d3": "^7.4.3", 26 | "@types/jest": "^29.5.12", 27 | "@types/vis": "^4.21.27", 28 | "@vitest/ui": "^1.4.0", 29 | "babel-jest": "^29.7.0", 30 | "esm": "^3.2.25", 31 | "hot-reload-extension-vite": "^1.0.13", 32 | "jest": "^29.7.0", 33 | "jest-environment-jsdom": "^29.7.0", 34 | "jest-preview": "^0.3.1", 35 | "jsdom": "^24.0.0", 36 | "rollup-plugin-chrome-extension": "^3.6.12", 37 | "rollup-plugin-dev-server": "^0.4.3", 38 | "rollup-plugin-node-resolve": "^5.2.0", 39 | "rollup-plugin-sourcemaps": "^0.6.3", 40 | "rollup-plugin-svelte": "^7.0.0", 41 | "rollup-plugin-typescript2": "^0.36.0", 42 | "svelte": "^4.2.12", 43 | "svelte-check": "^3.6.6", 44 | "svelte-jester": "^3.0.0", 45 | "svelte-listener": "git+https://github.com/RedHatter/svelte-listener.git", 46 | "ts-jest": "^29.1.2", 47 | "ts-node": "^10.9.2", 48 | "tslib": "^2.6.2", 49 | "typescript": "^5.4.3", 50 | "vite": "^5.1.6" 51 | }, 52 | "dependencies": { 53 | "@types/node": "^20.11.27", 54 | "d3": "^7.9.0", 55 | "path": "^0.12.7", 56 | "rollup": "^2.79.1", 57 | "rollup-plugin-css-only": "^4.5.2", 58 | "svelte-splitpanes": "^0.8.0", 59 | "vis": "^4.21.0-EOL", 60 | "vis-network": "^9.1.9" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /static/ContentScripts/contentScriptIsolate.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the ISOLATED content script! This can communicate with our MAIN content 3 | script. It can also communicate with the Popup and Panel. So it's kind of like 4 | the middle man of this Chrome extension 5 | */ 6 | 7 | let port = null; 8 | // Listens to messages from ContentScriptMain 9 | // and forwards them to other parts of the extension 10 | window.addEventListener('message', async (msg) => { 11 | if ( 12 | typeof msg !== 'object' || 13 | msg === null || 14 | msg.data?.source !== 'contentScript.js' 15 | ) { 16 | return; 17 | } 18 | 19 | switch (msg.data.type) { 20 | case 'updateRootComponent': 21 | case 'returnRootComponent': 22 | case 'returnTempRoot': 23 | chrome.runtime.sendMessage({ 24 | type: msg.data.type, 25 | rootComponent: msg.data.rootComponent, 26 | }); 27 | break; 28 | case 'returnSvelteVersion': 29 | chrome.runtime.sendMessage({ 30 | type: msg.data.type, 31 | svelteVersion: msg.data.svelteVersion, 32 | }); 33 | break; 34 | case 'handleBrowserRefresh': 35 | chrome.runtime.sendMessage({ 36 | type: msg.data.type, 37 | }) 38 | break; 39 | default: 40 | break; 41 | } 42 | }); 43 | 44 | 45 | // Listens for a message from the Popup and Panel 46 | // Forwards them to ContentScriptMain/index.js 47 | 48 | chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { 49 | 50 | switch (request.message) { 51 | case 'getRootComponent': 52 | case 'getSvelteVersion': 53 | case 'handleClosedPanel': 54 | window.postMessage({ 55 | type: request.message, 56 | source: 'contentScriptIsolate.js', 57 | }); 58 | break; 59 | 60 | case 'injectState': 61 | window.postMessage({ 62 | type: request.message, 63 | newState: request.newState, 64 | componentId: request.componentId, 65 | source: 'contentScriptIsolate.js', 66 | }); 67 | break; 68 | 69 | case 'injectSnapshot': 70 | window.postMessage({ 71 | type: request.message, 72 | snapshot: request.snapshot, 73 | source: 'contentScriptIsolate.js', 74 | }); 75 | break; 76 | } 77 | }); 78 | 79 | -------------------------------------------------------------------------------- /src/lib/components/Editor/Props.svelte: -------------------------------------------------------------------------------- 1 | 60 | 61 | 62 | {#if currentProps.length} 63 |
    64 | {#each currentProps as { key, value } (key)} 65 | change(key, e.detail, value)} 71 | /> 72 | {/each} 73 |
74 | {:else} 75 |
None
76 | {/if} 77 | 78 | 86 | -------------------------------------------------------------------------------- /src/lib/components/Editor.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 | {#if currentData2 && currentData2.type === 'component'} 13 |
14 |

Props

15 | 19 |
20 | 21 | {@const events = currentData2.detail.listeners?.map((l) => { 22 | const suffix = l.modifiers?.length ? `|${l.modifiers.join('|')}` : ''; 23 | const value = { __is: 'function', source: l.handler }; 24 | return { key: l.event + suffix, value }; 25 | })} 26 |
27 |

Events

28 | 29 |
30 |
31 |

State

32 | 33 |
34 | 35 | 36 | {:else if (currentData2 && currentData2.type === 'block') || (currentData2 && currentData2.type === 'iteration')} 37 |
38 |

State

39 | 44 |
45 | 46 | 47 | {:else if currentData2 && currentData2.type === 'element'} 48 |
49 |

Attributes

50 | 55 |
56 | 57 | {@const events = currentData2.detail.listeners?.map((l) => { 58 | const suffix = l.modifiers?.length ? `|${l.modifiers.join('|')}` : ''; 59 | const value = { __is: 'function', source: l.handler }; 60 | return { key: l.event + suffix, value }; 61 | })} 62 |
63 |

Events

64 | 65 |
66 | {/if} 67 |
68 | 69 | 99 | -------------------------------------------------------------------------------- /__mocks__/chrome.ts: -------------------------------------------------------------------------------- 1 | // import { Component } from '../src/pages/Panel/slices/highlightedComponentSlice'; 2 | import initialData from "./mockData"; 3 | 4 | let data = JSON.parse(JSON.stringify(initialData)); 5 | 6 | type ChromeMessageListener = (message: any) => void; 7 | 8 | interface MockRuntime { 9 | onMessage: { 10 | addListener: (callback: ChromeMessageListener) => void; 11 | removeListener: (callback: ChromeMessageListener) => void; 12 | _triggerMessage: (message: any) => void; 13 | }; 14 | sendMessage: (message: MockMessageType) => void; 15 | } 16 | 17 | interface MockMessageType { 18 | message: 19 | | "getRootComponent" 20 | | "getSvelteVersion" 21 | | "handleClosedPanel" 22 | | "injectState" 23 | | "injectSnapshot"; 24 | snapshot?: 'component'; 25 | componentId?: number; 26 | newState?: { 27 | [stateKey: string]: number | string; 28 | }; 29 | } 30 | interface QueryInfo { 31 | active: boolean; 32 | lastFocusedWindow: boolean; 33 | } 34 | 35 | interface MockTabs { 36 | query: (queryInfo: QueryInfo) => [{ id: number; url: string }]; 37 | sendMessage: (tabId: number, message: MockMessageType) => void; 38 | } 39 | 40 | export interface MockChrome { 41 | runtime: MockRuntime; 42 | tabs: MockTabs; 43 | clearListeners: () => void; 44 | resetMockData: () => void; 45 | sendEmptyDataOnNextRequest: () => void; 46 | } 47 | 48 | let listeners: ChromeMessageListener[] = []; 49 | let _sendEmptyDataOnNextRequest = false; 50 | 51 | 52 | 53 | const chrome: MockChrome = { 54 | runtime: { 55 | onMessage: { 56 | addListener: (callback) => { 57 | listeners.push(callback); 58 | }, 59 | _triggerMessage: (message) => { 60 | listeners.forEach((callback) => callback(message)); 61 | }, 62 | removeListener: (callback) => { 63 | listeners = listeners.filter((c) => c === callback); 64 | }, 65 | }, 66 | sendMessage: function (message) {}, 67 | }, 68 | tabs: { 69 | query: (queryInfo: QueryInfo) => { 70 | return [{ id: 0, url: "" }]; 71 | }, 72 | sendMessage: (tabId, request) => { 73 | switch (request.message) { 74 | case "getRootComponent": 75 | { 76 | let message: {}; 77 | if (_sendEmptyDataOnNextRequest) { 78 | message = { 79 | type: "returnRootComponent", 80 | rootComponent: undefined, 81 | }; 82 | _sendEmptyDataOnNextRequest = false; 83 | } else { 84 | message = { 85 | type: "returnRootComponent", 86 | rootComponent: JSON.parse(JSON.stringify(data)), 87 | }; 88 | } 89 | listeners.forEach((f) => f(message)); 90 | } 91 | break; 92 | case "getSvelteVersion": 93 | { 94 | } 95 | break; 96 | case "handleClosedPanel": 97 | { 98 | } 99 | break; 100 | 101 | } 102 | }, 103 | }, 104 | clearListeners: function () { 105 | listeners = []; 106 | }, 107 | resetMockData: function () { 108 | data = JSON.parse(JSON.stringify(initialData)); 109 | }, 110 | sendEmptyDataOnNextRequest: function () { 111 | _sendEmptyDataOnNextRequest = true; 112 | }, 113 | }; 114 | 115 | export default chrome; 116 | -------------------------------------------------------------------------------- /src/lib/components/Editor/Expandable.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 | 48 | 49 |
  • (expanded = !expanded)} 56 | > 57 | {key}: 58 |   59 | 60 | {#if type === "string"} 61 | 62 | {:else if value == null || value !== value} 63 | 64 | {:else if type === "number" || type === "boolean"} 65 | 66 | {:else if Array.isArray(value)} 67 | Array [{value.length || ""}] 68 | 69 | {#if value.length && expanded} 70 |
      71 | {#each value as v, key} 72 | 77 | dispatch("change", stringify(value, key, e.detail))} 78 | /> 79 | {/each} 80 |
    81 | {/if} 82 | {:else if type === "object"} 83 | {#if value.__is === "function"} 84 | function {value.name || ""}() 85 | {#if expanded}
    {value.source}
    {/if} 86 | {:else if value.__is === "symbol"} 87 | {value.name || "Symbol()"} 88 | {:else if Object.keys(value).length} 89 | Object {…} 90 | {#if expanded} 91 |
      92 | {#each Object.entries(value) as [key, v] (key)} 93 | 98 | dispatch("change", stringify(value, key, e.detail))} 99 | /> 100 | {/each} 101 |
    102 | {/if} 103 | {:else} 104 | Object { } 105 | {/if} 106 | {/if} 107 |
  • 108 | 109 | 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

    3 | 4 | 5 | 6 |

    7 |

    SvelteScope

    8 | 9 | 10 | SvelteScope is a Google Chrome Extension specifically designed for Svelte webpages, offering developers a platform to test webpage behaviors and conduct component experimentation within the browser environment. Its features include an interactive visual tree diagram, real-time rendering of state changes, and the ability for developers to create multiple snapshots, all aimed at streamlining front-end testing processes. 11 | 12 | ## Key Features 13 | 14 | SvelteScope provides developers with an interactive tree diagram to explore component relationships, streamlining the comprehension of the application's structure. Additionally, developers can click on individual components to navigate their properties, state, and events. 15 | 16 |

    17 | Tree Diagram 18 |

    19 | 20 | 21 | Generate test data scenarios by adjusting component props directly on the SvelteScope app and observe immediate rendering updates directly within the webpage. 22 | 23 |

    24 | Editor Box 25 |

    26 | 27 | 28 | Front-end testing often involves extensive trials to pinpoint the source of issues. To facilitate tracking and comparison of tests, SvelteScope offers developers the ability to create multiple snapshots. Within SvelteScope, developers can organize these snapshots into tabs, enabling easy addition and removal as needed. 29 | 30 |

    31 | Snapshot 32 |

    33 | 34 |
    35 | 36 | ## Installation and Usage Guide for SvelteScope 37 | 38 | To install the SvelteScope developer tool onto Chrome browser, please visit Chrome Web Store. 39 | 40 | 41 | 42 | 43 | Chrome Web Store 44 | 45 | 46 | 47 | (Developer: If you're interested in contributing to this project, please refer to the Contribution section below for instructions) 48 | 49 | 50 | If you haven't already, enable the extension by following these steps: 51 | 1. Click on the three dots (⋮) located at the top right corner of the browser window to open the Chrome menu. 52 | 2. From the menu, select "More tools," then choose "Extensions." This will open the Extensions page. 53 | 3. Locate the extension you want to enable in the list of installed extensions. 54 | 4. Toggle the switch next to the extension to the right (gray to blue) to enable it. 55 | 56 | 57 | **Usuage Guidelines** 58 | 59 | SvelteScope only functions on locally hosted Svelte web pages. 60 | * Access the developer tools by right-clicking anywhere on the webpage and selecting "Inspect" from the context menu. 61 | * Locate and click on 'SvelteScope' in the top ribbon of the developer tools. 62 | * Explore webpages to observe the real-time rendering of a visual tree component for the Svelte App. 63 | * Select components in the visual tree diagram to access their states. 64 | * Modify component props, events, and states using the editor box, and observe live changes on the webpage. 65 | * Generate additional snapshots by clicking the "+" icon. Delete snapshots by clicking the "x" icon. Reset all tabs by clicking "reset". 66 | 67 | 68 |
    69 | 70 |

    Open Source Contribution

    71 | 72 | We greatly encourage contributions from the community. Your input, suggestions, and feedback play a crucial role in advancing the project. Below are ways in which you can contribute: 73 | 74 | - Review our code and provide feedback. 75 | - Test the extension and report any bugs or issues you encounter. 76 | - Suggest new features or improvements. 77 | - Submit pull requests to address open issues or implement new features. 78 | 79 | Instructions to download repository. 80 | 81 | 1. Access the SvelteScope GitHub repository. 82 | 2. Fork the repository to create personal working of the repository. 83 | 3. Clone or download the the fork to your local machine. 84 | 4. Install the dependencies 85 | ``` npm install ``` 86 | 5. Once the dependencies are installed, run the following command to build the Svelte extension to compile the code. 87 | ``` npm run build ``` 88 | 6. Save all changes. 89 | 7. Open Chrome and go to chrome://extensions/. 90 | 7. Enable developer mode by toggling the switch in the top right corner. 91 | 8. Click on "Load unpacked" in the top left corner. 92 | 9. Navigate to the directory where your local repository is located and select the build folder to load. 93 | 94 | ## Contributors 95 | - Branden Basche • [LinkedIn](https://www.linkedin.com/in/brandenbasche/) • [Github](https://github.com/brandenrbasche) 96 | - James Lee • [LinkedIn](https://www.linkedin.com/in/james-lee-a7b2842b6/) • [Github](https://github.com/alphajames258) 97 | - Binh Nguyen • [LinkedIn](https://www.linkedin.com/in/binh-nguyen-a07731101/) • [Github](https://github.com/binhnguyen96/) 98 | - Gregory Valentin • [LinkedIn](https://www.linkedin.com/in/gregory-valentin-a389b3221/) • [Github](https://github.com/punkygreg) 99 | - Donald Wong • [LinkedIn](https://www.linkedin.com/in/donald-wong-93702931) • [Github](https://github.com/dwong92) 100 | 101 |

    Visit us online 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /static/ContentScripts/svelte-listener/svelte.js: -------------------------------------------------------------------------------- 1 | import { add, update, remove } from "./listener.js"; 2 | import { updateProfile } from "./profiler.js"; 3 | 4 | const nodeMap = new Map(); 5 | let _id = 0; 6 | let currentBlock; 7 | 8 | export function getNode(id) { 9 | return nodeMap.get(id); 10 | } 11 | 12 | export function getAllNodes() { 13 | nodeMap.values(); 14 | } 15 | 16 | const rootNodes = []; 17 | export function getRootNodes() { 18 | return rootNodes; 19 | } 20 | 21 | let svelteVersion = null; 22 | export function getSvelteVersion() { 23 | return svelteVersion; 24 | } 25 | 26 | function addNode(node, target, anchor) { 27 | nodeMap.set(node.id, node); 28 | nodeMap.set(node.detail, node); 29 | 30 | let targetNode = nodeMap.get(target); 31 | if (!targetNode || targetNode.parentBlock != node.parentBlock) { 32 | targetNode = node.parentBlock; 33 | } 34 | 35 | node.parent = targetNode; 36 | 37 | const anchorNode = nodeMap.get(anchor); 38 | 39 | if (targetNode) { 40 | let index = -1; 41 | if (anchorNode) index = targetNode.children.indexOf(anchorNode); 42 | 43 | if (index != -1) { 44 | targetNode.children.splice(index, 0, node); 45 | } else { 46 | targetNode.children.push(node); 47 | } 48 | } else { 49 | rootNodes.push(node); 50 | } 51 | 52 | add(node, anchorNode); 53 | } 54 | 55 | function removeNode(node) { 56 | if (!node) return; 57 | 58 | nodeMap.delete(node.id); 59 | nodeMap.delete(node.detail); 60 | 61 | const index = node.parent.children.indexOf(node); 62 | node.parent.children.splice(index, 1); 63 | node.parent = null; 64 | 65 | remove(node); 66 | } 67 | 68 | function updateElement(element) { 69 | const node = nodeMap.get(element); 70 | if (!node) return; 71 | 72 | if (node.type == "anchor") node.type = "text"; 73 | 74 | update(node); 75 | } 76 | 77 | function insert(element, target, anchor) { 78 | const node = { 79 | id: _id++, 80 | type: 81 | element.nodeType == 1 82 | ? "element" 83 | : element.nodeValue && element.nodeValue != " " 84 | ? "text" 85 | : "anchor", 86 | detail: element, 87 | tagName: element.nodeName.toLowerCase(), 88 | parentBlock: currentBlock, 89 | children: [], 90 | }; 91 | addNode(node, target, anchor); 92 | 93 | for (const child of element.childNodes) { 94 | if (!nodeMap.has(child)) insert(child, element); 95 | } 96 | } 97 | 98 | function svelteRegisterComponent(e) { 99 | if (e.detail !== null) { 100 | const { component, tagName } = e.detail; 101 | 102 | const node = nodeMap.get(component.$$.fragment); 103 | if (node) { 104 | nodeMap.delete(component.$$.fragment); 105 | 106 | node.detail = component; 107 | node.tagName = tagName; 108 | 109 | update(node); 110 | } else { 111 | nodeMap.set(component.$$.fragment, { 112 | type: "component", 113 | detail: component, 114 | tagName, 115 | }); 116 | } 117 | } 118 | } 119 | 120 | // Ugly hack b/c promises are resolved/rejected outside of normal render flow 121 | let lastPromiseParent = null; 122 | function svelteRegisterBlock(e) { 123 | if (e.detail !== null) { 124 | const { type, id, block, ...detail } = e.detail; 125 | 126 | const tagName = type == "pending" ? "await" : type; 127 | const nodeId = _id++; 128 | 129 | if (block.m) { 130 | const mountFn = block.m; 131 | block.m = (target, anchor) => { 132 | const parentBlock = currentBlock; 133 | let node = { 134 | id: nodeId, 135 | type: "block", 136 | detail, 137 | tagName, 138 | parentBlock, 139 | children: [], 140 | }; 141 | 142 | switch (type) { 143 | case "then": 144 | case "catch": 145 | if (!node.parentBlock) node.parentBlock = lastPromiseParent; 146 | break; 147 | 148 | case "slot": 149 | node.type = "slot"; 150 | break; 151 | 152 | case "component": 153 | const componentNode = nodeMap.get(block); 154 | if (componentNode) { 155 | nodeMap.delete(block); 156 | Object.assign(node, componentNode); 157 | } else { 158 | Object.assign(node, { 159 | type: "component", 160 | tagName: "Unknown", 161 | detail: {}, 162 | }); 163 | nodeMap.set(block, node); 164 | } 165 | 166 | Promise.resolve().then( 167 | () => 168 | node.detail.$$ && 169 | Object.keys(node.detail.$$.bound).length && 170 | update(node) 171 | ); 172 | break; 173 | } 174 | 175 | if (type == "each") { 176 | let group = nodeMap.get(parentBlock.id + id); 177 | if (!group) { 178 | group = { 179 | id: _id++, 180 | type: "block", 181 | detail: { 182 | ctx: {}, 183 | source: detail.source, 184 | }, 185 | tagName: "each", 186 | parentBlock, 187 | children: [], 188 | }; 189 | nodeMap.set(parentBlock.id + id, group); 190 | addNode(group, target, anchor); 191 | } 192 | node.parentBlock = group; 193 | node.type = "iteration"; 194 | addNode(node, group, anchor); 195 | } else { 196 | addNode(node, target, anchor); 197 | } 198 | 199 | currentBlock = node; 200 | updateProfile(node, "mount", mountFn, target, anchor); 201 | currentBlock = parentBlock; 202 | }; 203 | } 204 | 205 | if (block.p) { 206 | const patchFn = block.p; 207 | block.p = (changed, ctx) => { 208 | const parentBlock = currentBlock; 209 | currentBlock = nodeMap.get(nodeId); 210 | 211 | update(currentBlock); 212 | 213 | updateProfile(currentBlock, "patch", patchFn, changed, ctx); 214 | 215 | currentBlock = parentBlock; 216 | }; 217 | } 218 | 219 | if (block.d) { 220 | const detachFn = block.d; 221 | block.d = (detaching) => { 222 | const node = nodeMap.get(nodeId); 223 | 224 | if (node) { 225 | if (node.tagName == "await") lastPromiseParent = node.parentBlock; 226 | 227 | removeNode(node); 228 | } 229 | 230 | updateProfile(node, "detach", detachFn, detaching); 231 | }; 232 | } 233 | } 234 | } 235 | 236 | function svelteDOMInsert(e) { 237 | if (e.detail !== null) { 238 | const { node: element, target, anchor } = e.detail; 239 | 240 | insert(element, target, anchor); 241 | } 242 | } 243 | 244 | function svelteDOMRemove(e) { 245 | if (e.detail !== null) { 246 | const node = nodeMap.get(e.detail.node); 247 | if (!node) return; 248 | 249 | removeNode(node); 250 | } 251 | } 252 | 253 | function svelteDOMAddEventListener(e) { 254 | if (e.detail !== null) { 255 | const { node, ...detail } = e.detail; 256 | 257 | if (!node.__listeners) node.__listeners = []; 258 | 259 | node.__listeners.push(detail); 260 | } 261 | } 262 | 263 | function svelteDOMRemoveEventListener(e) { 264 | if(e.detail !== null){ 265 | const { node, event, handler, modifiers } = e.detail; 266 | 267 | if (!node.__listeners) return; 268 | 269 | const index = node.__listeners.findIndex( 270 | (o) => o.event == event && o.handler == handler && o.modifiers == modifiers 271 | ); 272 | 273 | if (index == -1) return; 274 | 275 | node.__listeners.splice(index, 1); 276 | } 277 | } 278 | 279 | function svelteUpdateNode(e) { 280 | if (e.detail !== null) { 281 | updateElement(e.detail.node); 282 | } 283 | } 284 | 285 | function setup(root) { 286 | root.addEventListener("SvelteRegisterBlock", (e) => { 287 | if (e.detail !== null) { 288 | (svelteVersion = e.detail.version), { once: true }; 289 | } 290 | }); 291 | 292 | root.addEventListener("SvelteRegisterComponent", svelteRegisterComponent); 293 | root.addEventListener("SvelteRegisterBlock", svelteRegisterBlock); 294 | root.addEventListener("SvelteDOMInsert", svelteDOMInsert); 295 | root.addEventListener("SvelteDOMRemove", svelteDOMRemove); 296 | root.addEventListener("SvelteDOMAddEventListener", svelteDOMAddEventListener); 297 | root.addEventListener( 298 | "SvelteDOMRemoveEventListener", 299 | svelteDOMRemoveEventListener 300 | ); 301 | root.addEventListener("SvelteDOMSetData", svelteUpdateNode); 302 | root.addEventListener("SvelteDOMSetProperty", svelteUpdateNode); 303 | root.addEventListener("SvelteDOMSetAttribute", svelteUpdateNode); 304 | root.addEventListener("SvelteDOMRemoveAttribute", svelteUpdateNode); 305 | } 306 | 307 | setup(window.document); 308 | for (let i = 0; i < window.frames.length; i++) { 309 | const frame = window.frames[i]; 310 | const root = frame.document; 311 | setup(root); 312 | const timer = setInterval(() => { 313 | if (root == frame.document) return; 314 | clearTimeout(timer); 315 | setup(frame.document); 316 | }, 0); 317 | root.addEventListener("readystatechange", (e) => clearTimeout(timer), { 318 | once: true, 319 | }); 320 | } 321 | -------------------------------------------------------------------------------- /static/ContentScripts/contentScript.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the MAIN content script! This is the script that gets injected into 3 | whatever webpage you're on. This means it not only has the ability to access 4 | the DOM, it can listen for events that get emitted from developer builds of 5 | Svelte applications. 6 | */ 7 | 8 | // import { getNode, getSvelteVersion, getRootNodes } from "svelte-listener"; 9 | import { getNode, getSvelteVersion, getRootNodes } from './svelte-listener/index' 10 | 11 | console.log('You are using SvelteScope: Svelte Debugging Tool') 12 | 13 | // @ts-ignore - possibly find an alternative 14 | window.__svelte_devtools_inject_state = function (id, key, value) { 15 | const { detail: component } = getNode(id) || {}; 16 | if (typeof value === "object") { 17 | component && component.$inject_state({ [key]: value }); 18 | } 19 | component && component.$inject_state({ [key]: value }); 20 | 21 | }; 22 | 23 | 24 | 25 | 26 | function clone(value, seen = new Map()) { 27 | switch (typeof value) { 28 | case "function": 29 | return { __isFunction: true, source: value.toString(), name: value.name }; 30 | case "symbol": 31 | return { __isSymbol: true, name: value.toString() }; 32 | case "object": { 33 | if (value === window || value === null) return null; 34 | if (Array.isArray(value)) return value.map((o) => clone(o, seen)); 35 | if (seen.has(value)) return {}; 36 | const o = {}; 37 | seen.set(value, o); 38 | for (const [key, v] of Object.entries(value)) { 39 | o[key] = clone(v, seen); 40 | } 41 | return o; 42 | } 43 | default: 44 | return value; 45 | } 46 | } 47 | 48 | function gte(major, minor, patch) { 49 | const version = (getSvelteVersion() || "0.0.0") 50 | .split(".") 51 | .map((n) => parseInt(n)); 52 | return ( 53 | version[0] > major || 54 | (version[0] == major && 55 | (version[1] > minor || (version[1] == minor && version[2] >= patch))) 56 | ); 57 | } 58 | 59 | let _shouldUseCapture = null; 60 | function shouldUseCapture() { 61 | return _shouldUseCapture == null 62 | ? (_shouldUseCapture = gte(3, 19, 2)) 63 | : _shouldUseCapture; 64 | } 65 | 66 | // This is a global variable to let us know if the page has been loaded or not 67 | let pageLoaded = false; 68 | 69 | // At this time, this content script only gets Svelte component data once 70 | window.addEventListener("load", (event) => { 71 | pageLoaded = true; 72 | }); 73 | 74 | // Gets the root component from svelte listener and returns 75 | // a component tree starting with the root component 76 | function traverseComponent(node) { 77 | let components = []; 78 | node.children.forEach((child) => { 79 | if (child.type === "component" && child.detail.$$) { 80 | const serialized = { 81 | id: child.id, 82 | type: child.type, 83 | tagName: child.tagName, 84 | children: traverseComponent(child), 85 | }; 86 | 87 | const internal = child.detail.$$; 88 | const props = Array.isArray(internal.props) 89 | ? internal.props // Svelte < 3.13.0 stored props names as an array 90 | : Object.keys(internal.props); 91 | let ctx = clone( 92 | shouldUseCapture() ? child.detail.$capture_state() : internal.ctx 93 | ); 94 | if (ctx === undefined) ctx = {}; 95 | serialized.detail = { 96 | attributes: props.flatMap((key) => { 97 | const value = ctx[key]; 98 | delete ctx[key]; 99 | return value === undefined 100 | ? [] 101 | : { key, value, isBound: key in internal.bound }; 102 | }), 103 | listeners: Object.entries(internal.callbacks).flatMap( 104 | ([event, value]) => 105 | value.map((o) => ({ event, handler: o.toString() })) 106 | ), 107 | ctx: Object.entries(ctx).map(([key, value]) => ({ key, value })), 108 | }; 109 | components.push(serialized); 110 | } else { 111 | components = components.concat(traverseComponent(child)); 112 | } 113 | }); 114 | return components; 115 | } 116 | 117 | // Gets component tree using svelte listener and sends it to the dev tool panel 118 | function sendRootNodeToExtension(messageType) { 119 | if ( 120 | messageType !== "updateRootComponent" && 121 | messageType !== "returnRootComponent" && 122 | messageType !== "returnTempRoot" 123 | ) { 124 | return; 125 | } 126 | 127 | 128 | const rootNodes = getRootNodes(); 129 | const newRootNodes = traverseComponent({ 130 | children: rootNodes, 131 | type: "component", 132 | }); 133 | if (!newRootNodes) { 134 | return; 135 | } 136 | const newRootNode = newRootNodes[0]; 137 | window.postMessage({ 138 | type: messageType, 139 | rootComponent: newRootNode, 140 | source: "contentScript.js", 141 | }); 142 | } 143 | 144 | // Gets svelte version using svelte listener and sends it to 145 | // the Popup box 146 | function sendSvelteVersionToExtension() { 147 | const svelteVersion = getSvelteVersion(); 148 | if (!svelteVersion) { 149 | return; 150 | } 151 | 152 | // Sends a message to ContentScriptIsolated/index.js 153 | window.postMessage({ 154 | type: "returnSvelteVersion", 155 | svelteVersion: svelteVersion, 156 | source: "contentScript.js", 157 | }); 158 | } 159 | 160 | function injectState(id, newState) { 161 | recentlyUpdated = true; 162 | setTimeout(() => { 163 | recentlyUpdated = false; 164 | }, 0); 165 | const component = id.detail; 166 | component.$inject_state(newState); 167 | 168 | /* 169 | Sends a new snapshot to the panel. If there is a state change that doesn't 170 | modify the DOM, the event listeners won't hear any events. Let's just send 171 | back a new updated root node for all state injections 172 | */ 173 | sendRootNodeToExtension("updateRootComponent"); 174 | } 175 | 176 | // When state is injected, an event is emitted by the Svelte app 177 | // This forces the app to ignore those updates 178 | let recentlyInjectedASnapshot = false; 179 | function injectSnapshot(snapshot) { 180 | recentlyInjectedASnapshot = true; 181 | const listOfIds = []; 182 | const listOfStates = []; 183 | function getComponentData(component) { 184 | listOfIds.push(component.id); 185 | const newState = {}; 186 | component.detail.ctx.forEach((i) => { 187 | newState[i.key] = i.value; 188 | }); 189 | listOfStates.push(newState); 190 | component.children.forEach((child) => { 191 | getComponentData(child); 192 | }); 193 | } 194 | getComponentData(snapshot); 195 | for (let i = 0; i < listOfIds.length; i++) { 196 | const component = getNode(listOfIds[i]); 197 | if (component) { 198 | const detail = component.detail; 199 | detail.$inject_state(listOfStates[i]); 200 | } 201 | } 202 | // When state is injected, an event is emitted by the Svelte app 203 | // This forces the app to ignore those updates 204 | recentlyUpdated = true; 205 | setTimeout(() => { 206 | recentlyUpdated = false; 207 | recentlyInjectedASnapshot = false; 208 | }, 0); 209 | } 210 | 211 | //-------------------------------------------------------------------------------------- 212 | let readyForUpdates = false; 213 | // Listens to events from ContentScriptIsolated/index.js and responds based on 214 | // the event's type 215 | window.addEventListener("message", async (msg) => { 216 | if ( 217 | typeof msg !== "object" || 218 | msg === null || 219 | msg.data?.source !== "contentScriptIsolate.js" 220 | ) { 221 | return; 222 | } 223 | 224 | const data = msg.data; 225 | 226 | switch (data.type) { 227 | case "getSvelteVersion": 228 | sendSvelteVersionToExtension(); 229 | break; 230 | case "getRootComponent": 231 | readyForUpdates = true; 232 | sendRootNodeToExtension("returnRootComponent"); 233 | break; 234 | case "handleClosedPanel": 235 | readyForUpdates = false; 236 | break; 237 | case "injectState": 238 | injectState(data.componentId, data.newState); 239 | break; 240 | case "injectSnapshot": 241 | injectSnapshot(data.snapshot); 242 | // Before this setTimeout was added, we were sending back 243 | // a snapshot that hadn't been updated yet 244 | setTimeout(() => { 245 | sendRootNodeToExtension("returnTempRoot"); 246 | }, 0); 247 | break; 248 | } 249 | }); 250 | 251 | //-------------------------------------------------------------------------------------- 252 | 253 | // Whenever nodes are updated, typically a bunch get updated at the same time 254 | // I need to throttle updates so when I get a bunch at once, I only send the 255 | // LATEST update 256 | let recentlyUpdated = false; 257 | function sendUpdateToPanel() { 258 | // This should only happen after the DOM is fully loaded 259 | // And after the Panel is loaded. 260 | // if (!pageLoaded || !readyForUpdates) return; 261 | if (!pageLoaded || !readyForUpdates) return; 262 | if (recentlyInjectedASnapshot) return; 263 | // This needs a setTimeout because it MUST run AFTER the svelte-listener events fire 264 | // Send the devtool panel an updated root component whenever the Svelte DOM changes 265 | if (recentlyUpdated === false) { 266 | setTimeout(() => { 267 | recentlyUpdated = false; 268 | sendRootNodeToExtension("updateRootComponent"); 269 | }, 0); 270 | recentlyUpdated = true; 271 | } 272 | } 273 | 274 | window.document.addEventListener("SvelteRegisterComponent", sendUpdateToPanel); 275 | window.document.addEventListener("SvelteRegisterBlock", sendUpdateToPanel); 276 | window.document.addEventListener("SvelteDOMInsert", (e) => { 277 | sendUpdateToPanel; 278 | }); 279 | window.document.addEventListener("SvelteDOMRemove", sendUpdateToPanel); 280 | window.document.addEventListener("SvelteDOMSetData", sendUpdateToPanel); 281 | window.document.addEventListener("SvelteDOMSetProperty", sendUpdateToPanel); 282 | window.document.addEventListener("SvelteDOMSetAttribute", sendUpdateToPanel); 283 | 284 | 285 | -------------------------------------------------------------------------------- /src/lib/components/Tree.svelte: -------------------------------------------------------------------------------- 1 | 295 | 296 |

    328 | 329 | 368 | -------------------------------------------------------------------------------- /src/lib/containers/TabAdder.svelte: -------------------------------------------------------------------------------- 1 | 282 | 283 |
    284 |
    285 | 292 | 299 | 300 |

    SvelteScope

    301 |
    302 | 303 | 324 |
    325 | 326 |
      327 | {#each Object.entries(items) as [key, value]} 328 |
    • 329 | {key} 330 |
    • 331 | {/each} 332 | + 333 |
    334 | 335 | {#each Object.entries(items) as [key, value]} 336 | {#if activeTabValue == value.value} 337 |
    338 |
    339 | {#if currentdata !== undefined} 340 |

    341 | Currently editing: {currentdata} 344 |

    345 | {/if} 346 | {#if currentdata === undefined} 347 |

    348 | Currently editing: {root} 349 |

    350 | {/if} 351 | {#if Object.keys(items).length > 1} 352 | 353 | {:else} 354 | 360 | {/if} 361 |
    362 | 363 |
    364 | {/if} 365 | {/each} 366 | 367 | 562 | -------------------------------------------------------------------------------- /__mocks__/mockData.ts: -------------------------------------------------------------------------------- 1 | const mockData = { 2 | id: 25, 3 | type: 'component', 4 | tagName: 'App', 5 | children: [ 6 | { 7 | id: 24, 8 | type: 'component', 9 | tagName: 'Board', 10 | children: [ 11 | { 12 | id: 6, 13 | type: 'component', 14 | tagName: 'Row', 15 | children: [ 16 | { 17 | id: 0, 18 | type: 'component', 19 | tagName: 'Box', 20 | children: [], 21 | detail: { 22 | attributes: [ 23 | { key: 'boxState', value: '-', isBound: false }, 24 | { 25 | key: 'handleClick', 26 | value: { 27 | __isFunction: true, 28 | source: 29 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 30 | name: 'handleClick', 31 | }, 32 | isBound: false, 33 | }, 34 | { key: 'rowIndex', value: 0, isBound: false }, 35 | { key: 'boxIndex', value: 0, isBound: false }, 36 | ], 37 | listeners: [], 38 | ctx: [{ key: 'state', value: '' }], 39 | }, 40 | }, 41 | { 42 | id: 2, 43 | type: 'component', 44 | tagName: 'Box', 45 | children: [], 46 | detail: { 47 | attributes: [ 48 | { key: 'boxState', value: '-', isBound: false }, 49 | { 50 | key: 'handleClick', 51 | value: { 52 | __isFunction: true, 53 | source: 54 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 55 | name: 'handleClick', 56 | }, 57 | isBound: false, 58 | }, 59 | { key: 'rowIndex', value: 0, isBound: false }, 60 | { key: 'boxIndex', value: 1, isBound: false }, 61 | ], 62 | listeners: [], 63 | ctx: [{ key: 'state', value: '' }], 64 | }, 65 | }, 66 | { 67 | id: 4, 68 | type: 'component', 69 | tagName: 'Box', 70 | children: [], 71 | detail: { 72 | attributes: [ 73 | { key: 'boxState', value: '-', isBound: false }, 74 | { 75 | key: 'handleClick', 76 | value: { 77 | __isFunction: true, 78 | source: 79 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 80 | name: 'handleClick', 81 | }, 82 | isBound: false, 83 | }, 84 | { key: 'rowIndex', value: 0, isBound: false }, 85 | { key: 'boxIndex', value: 2, isBound: false }, 86 | ], 87 | listeners: [], 88 | ctx: [{ key: 'state', value: '' }], 89 | }, 90 | }, 91 | ], 92 | detail: { 93 | attributes: [ 94 | { key: 'rowState', value: ['-', '-', '-'], isBound: false }, 95 | { 96 | key: 'handleClick', 97 | value: { 98 | __isFunction: true, 99 | source: 100 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 101 | name: 'handleClick', 102 | }, 103 | isBound: false, 104 | }, 105 | { key: 'rowIndex', value: 0, isBound: false }, 106 | ], 107 | listeners: [], 108 | ctx: [ 109 | { 110 | key: 'Box', 111 | value: { 112 | __isFunction: true, 113 | source: 114 | "class Box extends SvelteComponentDev {\n \tconstructor(options) {\n \t\tsuper(options);\n\n \t\tinit(this, options, instance$4, create_fragment$4, safe_not_equal, {\n \t\t\tboxState: 0,\n \t\t\thandleClick: 1,\n \t\t\trowIndex: 2,\n \t\t\tboxIndex: 3\n \t\t});\n\n \t\tdispatch_dev(\"SvelteRegisterComponent\", {\n \t\t\tcomponent: this,\n \t\t\ttagName: \"Box\",\n \t\t\toptions,\n \t\t\tid: create_fragment$4.name\n \t\t});\n \t}\n\n \tget boxState() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset boxState(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget handleClick() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset handleClick(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget rowIndex() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset rowIndex(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget boxIndex() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset boxIndex(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n }", 115 | name: 'Box', 116 | }, 117 | }, 118 | { key: 'stateeee', value: '' }, 119 | ], 120 | }, 121 | }, 122 | { 123 | id: 14, 124 | type: 'component', 125 | tagName: 'Row', 126 | children: [ 127 | { 128 | id: 8, 129 | type: 'component', 130 | tagName: 'Box', 131 | children: [], 132 | detail: { 133 | attributes: [ 134 | { key: 'boxState', value: '-', isBound: false }, 135 | { 136 | key: 'handleClick', 137 | value: { 138 | __isFunction: true, 139 | source: 140 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 141 | name: 'handleClick', 142 | }, 143 | isBound: false, 144 | }, 145 | { key: 'rowIndex', value: 1, isBound: false }, 146 | { key: 'boxIndex', value: 0, isBound: false }, 147 | ], 148 | listeners: [], 149 | ctx: [{ key: 'state', value: '' }], 150 | }, 151 | }, 152 | { 153 | id: 10, 154 | type: 'component', 155 | tagName: 'Box', 156 | children: [], 157 | detail: { 158 | attributes: [ 159 | { key: 'boxState', value: '-', isBound: false }, 160 | { 161 | key: 'handleClick', 162 | value: { 163 | __isFunction: true, 164 | source: 165 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 166 | name: 'handleClick', 167 | }, 168 | isBound: false, 169 | }, 170 | { key: 'rowIndex', value: 1, isBound: false }, 171 | { key: 'boxIndex', value: 1, isBound: false }, 172 | ], 173 | listeners: [], 174 | ctx: [{ key: 'state', value: '' }], 175 | }, 176 | }, 177 | { 178 | id: 12, 179 | type: 'component', 180 | tagName: 'Box', 181 | children: [], 182 | detail: { 183 | attributes: [ 184 | { key: 'boxState', value: '-', isBound: false }, 185 | { 186 | key: 'handleClick', 187 | value: { 188 | __isFunction: true, 189 | source: 190 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 191 | name: 'handleClick', 192 | }, 193 | isBound: false, 194 | }, 195 | { key: 'rowIndex', value: 1, isBound: false }, 196 | { key: 'boxIndex', value: 2, isBound: false }, 197 | ], 198 | listeners: [], 199 | ctx: [{ key: 'state', value: '' }], 200 | }, 201 | }, 202 | ], 203 | detail: { 204 | attributes: [ 205 | { key: 'rowState', value: ['-', '-', '-'], isBound: false }, 206 | { 207 | key: 'handleClick', 208 | value: { 209 | __isFunction: true, 210 | source: 211 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 212 | name: 'handleClick', 213 | }, 214 | isBound: false, 215 | }, 216 | { key: 'rowIndex', value: 1, isBound: false }, 217 | ], 218 | listeners: [], 219 | ctx: [ 220 | { 221 | key: 'Box', 222 | value: { 223 | __isFunction: true, 224 | source: 225 | "class Box extends SvelteComponentDev {\n \tconstructor(options) {\n \t\tsuper(options);\n\n \t\tinit(this, options, instance$4, create_fragment$4, safe_not_equal, {\n \t\t\tboxState: 0,\n \t\t\thandleClick: 1,\n \t\t\trowIndex: 2,\n \t\t\tboxIndex: 3\n \t\t});\n\n \t\tdispatch_dev(\"SvelteRegisterComponent\", {\n \t\t\tcomponent: this,\n \t\t\ttagName: \"Box\",\n \t\t\toptions,\n \t\t\tid: create_fragment$4.name\n \t\t});\n \t}\n\n \tget boxState() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset boxState(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget handleClick() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset handleClick(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget rowIndex() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset rowIndex(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget boxIndex() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset boxIndex(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n }", 226 | name: 'Box', 227 | }, 228 | }, 229 | { key: 'stateeee', value: '' }, 230 | ], 231 | }, 232 | }, 233 | { 234 | id: 22, 235 | type: 'component', 236 | tagName: 'Row', 237 | children: [ 238 | { 239 | id: 16, 240 | type: 'component', 241 | tagName: 'Box', 242 | children: [], 243 | detail: { 244 | attributes: [ 245 | { key: 'boxState', value: '-', isBound: false }, 246 | { 247 | key: 'handleClick', 248 | value: { 249 | __isFunction: true, 250 | source: 251 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 252 | name: 'handleClick', 253 | }, 254 | isBound: false, 255 | }, 256 | { key: 'rowIndex', value: 2, isBound: false }, 257 | { key: 'boxIndex', value: 0, isBound: false }, 258 | ], 259 | listeners: [], 260 | ctx: [{ key: 'state', value: '' }], 261 | }, 262 | }, 263 | { 264 | id: 18, 265 | type: 'component', 266 | tagName: 'Box', 267 | children: [], 268 | detail: { 269 | attributes: [ 270 | { key: 'boxState', value: '-', isBound: false }, 271 | { 272 | key: 'handleClick', 273 | value: { 274 | __isFunction: true, 275 | source: 276 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 277 | name: 'handleClick', 278 | }, 279 | isBound: false, 280 | }, 281 | { key: 'rowIndex', value: 2, isBound: false }, 282 | { key: 'boxIndex', value: 1, isBound: false }, 283 | ], 284 | listeners: [], 285 | ctx: [{ key: 'state', value: '' }], 286 | }, 287 | }, 288 | { 289 | id: 20, 290 | type: 'component', 291 | tagName: 'Box', 292 | children: [], 293 | detail: { 294 | attributes: [ 295 | { key: 'boxState', value: '-', isBound: false }, 296 | { 297 | key: 'handleClick', 298 | value: { 299 | __isFunction: true, 300 | source: 301 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 302 | name: 'handleClick', 303 | }, 304 | isBound: false, 305 | }, 306 | { key: 'rowIndex', value: 2, isBound: false }, 307 | { key: 'boxIndex', value: 2, isBound: false }, 308 | ], 309 | listeners: [], 310 | ctx: [{ key: 'state', value: '' }], 311 | }, 312 | }, 313 | ], 314 | detail: { 315 | attributes: [ 316 | { key: 'rowState', value: ['-', '-', '-'], isBound: false }, 317 | { 318 | key: 'handleClick', 319 | value: { 320 | __isFunction: true, 321 | source: 322 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 323 | name: 'handleClick', 324 | }, 325 | isBound: false, 326 | }, 327 | { key: 'rowIndex', value: 2, isBound: false }, 328 | ], 329 | listeners: [], 330 | ctx: [ 331 | { 332 | key: 'Box', 333 | value: { 334 | __isFunction: true, 335 | source: 336 | "class Box extends SvelteComponentDev {\n \tconstructor(options) {\n \t\tsuper(options);\n\n \t\tinit(this, options, instance$4, create_fragment$4, safe_not_equal, {\n \t\t\tboxState: 0,\n \t\t\thandleClick: 1,\n \t\t\trowIndex: 2,\n \t\t\tboxIndex: 3\n \t\t});\n\n \t\tdispatch_dev(\"SvelteRegisterComponent\", {\n \t\t\tcomponent: this,\n \t\t\ttagName: \"Box\",\n \t\t\toptions,\n \t\t\tid: create_fragment$4.name\n \t\t});\n \t}\n\n \tget boxState() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset boxState(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget handleClick() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset handleClick(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget rowIndex() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset rowIndex(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget boxIndex() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset boxIndex(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n }", 337 | name: 'Box', 338 | }, 339 | }, 340 | { key: 'stateeee', value: '' }, 341 | ], 342 | }, 343 | }, 344 | ], 345 | detail: { 346 | attributes: [ 347 | { 348 | key: 'handleClick', 349 | value: { 350 | __isFunction: true, 351 | source: 352 | "function handleClick(x, y) {\n \t\tif (state[x][y] !== '-') return;\n \t\tif (gameOver) return;\n \t\tturnCount++;\n\n \t\tif (turnCount === 9) {\n \t\t\t$$invalidate(2, gameOver = true);\n \t\t\tsetWinner('draw');\n \t\t}\n\n \t\t$$invalidate(1, state[x][y] = turn, state);\n \t\tconst test = [];\n\n \t\tfor (let x = 0; x < state.length; x++) {\n \t\t\tfor (let y = 0; y < state[0].length; y++) {\n \t\t\t\ttest.push(state[x][y]);\n \t\t\t}\n \t\t}\n\n \t\twinningConditions.forEach(c => {\n \t\t\tif (test[c[0]] === test[c[1]] && test[c[1]] === test[c[2]] && test[c[0]] === test[c[2]] && test[c[0]] !== '-') {\n \t\t\t\t$$invalidate(2, gameOver = true);\n \t\t\t\tsetWinner(turn);\n \t\t\t}\n \t\t});\n\n \t\tturn = turn === 'X' ? 'O' : 'X';\n \t}", 353 | name: 'handleClick', 354 | }, 355 | isBound: false, 356 | }, 357 | ], 358 | listeners: [], 359 | ctx: [ 360 | { 361 | key: 'Row', 362 | value: { 363 | __isFunction: true, 364 | source: 365 | "class Row extends SvelteComponentDev {\n \tconstructor(options) {\n \t\tsuper(options);\n \t\tinit(this, options, instance$3, create_fragment$3, safe_not_equal, { rowState: 0, handleClick: 1, rowIndex: 2 });\n\n \t\tdispatch_dev(\"SvelteRegisterComponent\", {\n \t\t\tcomponent: this,\n \t\t\ttagName: \"Row\",\n \t\t\toptions,\n \t\t\tid: create_fragment$3.name\n \t\t});\n \t}\n\n \tget rowState() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset rowState(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget handleClick() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset handleClick(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tget rowIndex() {\n \t\tthrow new Error(\": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n\n \tset rowIndex(value) {\n \t\tthrow new Error(\": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''\");\n \t}\n }", 366 | name: 'Row', 367 | }, 368 | }, 369 | { 370 | key: 'WinnerMessage', 371 | value: { 372 | __isFunction: true, 373 | source: 374 | 'class WinnerMessage extends SvelteComponentDev {\n \tconstructor(options) {\n \t\tsuper(options);\n \t\tinit(this, options, instance$2, create_fragment$2, safe_not_equal, { winner: 0 });\n\n \t\tdispatch_dev("SvelteRegisterComponent", {\n \t\t\tcomponent: this,\n \t\t\ttagName: "WinnerMessage",\n \t\t\toptions,\n \t\t\tid: create_fragment$2.name\n \t\t});\n \t}\n\n \tget winner() {\n \t\tthrow new Error(": Props cannot be read directly from the component instance unless compiling with \'accessors: true\' or \'\'");\n \t}\n\n \tset winner(value) {\n \t\tthrow new Error(": Props cannot be set directly on the component instance unless compiling with \'accessors: true\' or \'\'");\n \t}\n }', 375 | name: 'WinnerMessage', 376 | }, 377 | }, 378 | { 379 | key: 'winningConditions', 380 | value: [ 381 | [0, 1, 2], 382 | [3, 4, 5], 383 | [6, 7, 8], 384 | [0, 3, 6], 385 | [1, 4, 7], 386 | [2, 5, 8], 387 | [0, 4, 8], 388 | [2, 4, 6], 389 | ], 390 | }, 391 | { 392 | key: 'state', 393 | value: [ 394 | ['-', '-', '-'], 395 | ['-', '-', '-'], 396 | ['-', '-', '-'], 397 | ], 398 | }, 399 | { key: 'turnCount', value: 0 }, 400 | { key: 'turn', value: 'X' }, 401 | { key: 'gameOver', value: false }, 402 | { key: 'winner', value: null }, 403 | { 404 | key: 'setWinner', 405 | value: { 406 | __isFunction: true, 407 | source: 408 | 'function setWinner(newWinner) {\n \t\t$$invalidate(3, winner = newWinner);\n \t}', 409 | name: 'setWinner', 410 | }, 411 | }, 412 | ], 413 | }, 414 | }, 415 | ], 416 | detail: { 417 | attributes: [{ key: 'name', value: 'Tic Tac Toe', isBound: false }], 418 | listeners: [], 419 | ctx: [ 420 | { 421 | key: 'Board', 422 | value: { 423 | __isFunction: true, 424 | source: 425 | 'class Board extends SvelteComponentDev {\n \tconstructor(options) {\n \t\tsuper(options);\n \t\tinit(this, options, instance$1, create_fragment$1, safe_not_equal, { handleClick: 0 });\n\n \t\tdispatch_dev("SvelteRegisterComponent", {\n \t\t\tcomponent: this,\n \t\t\ttagName: "Board",\n \t\t\toptions,\n \t\t\tid: create_fragment$1.name\n \t\t});\n \t}\n\n \tget handleClick() {\n \t\treturn this.$$.ctx[0];\n \t}\n\n \tset handleClick(value) {\n \t\tthrow new Error(": Props cannot be set directly on the component instance unless compiling with \'accessors: true\' or \'\'");\n \t}\n }', 426 | name: 'Board', 427 | }, 428 | }, 429 | { 430 | key: 'WinnerMessage', 431 | value: { 432 | __isFunction: true, 433 | source: 434 | 'class WinnerMessage extends SvelteComponentDev {\n \tconstructor(options) {\n \t\tsuper(options);\n \t\tinit(this, options, instance$2, create_fragment$2, safe_not_equal, { winner: 0 });\n\n \t\tdispatch_dev("SvelteRegisterComponent", {\n \t\t\tcomponent: this,\n \t\t\ttagName: "WinnerMessage",\n \t\t\toptions,\n \t\t\tid: create_fragment$2.name\n \t\t});\n \t}\n\n \tget winner() {\n \t\tthrow new Error(": Props cannot be read directly from the component instance unless compiling with \'accessors: true\' or \'\'");\n \t}\n\n \tset winner(value) {\n \t\tthrow new Error(": Props cannot be set directly on the component instance unless compiling with \'accessors: true\' or \'\'");\n \t}\n }', 435 | name: 'WinnerMessage', 436 | }, 437 | }, 438 | ], 439 | }, 440 | }; 441 | 442 | export default mockData; 443 | --------------------------------------------------------------------------------