// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assert } from 'chai';
import sinon from 'sinon';
import * as Common from '../../core/common/common.js';
import * as Platform from '../../core/platform/platform.js';
import * as TextUtils from '../../core/text_utils/text_utils.js';
import { setupLocaleHooks } from '../../testing/LocaleHelpers.js';
import { MockDebuggerBackend } from '../../testing/MockScopeChain.js';
import { setupRuntimeHooks } from '../../testing/RuntimeHelpers.js';
import { createContentProviderUISourceCode, createContentProviderUISourceCodes, createFileSystemUISourceCode, } from '../../testing/UISourceCodeHelpers.js';
import * as Workspace from '../workspace/workspace.js';
import * as Persistence from './persistence.js';
const { urlString } = Platform.DevToolsPath;
describe('Automapping', () => {
    setupLocaleHooks();
    setupRuntimeHooks();
    let backend;
    beforeEach(() => {
        backend = new MockDebuggerBackend();
        // Eagerly instantiate persistence so it registers listeners on the workspace before we add files.
        void backend.universe.persistence;
    });
    it('correctly maps sourcemap sources even when compiled URL matches one of the source URLs', async () => {
        const url = urlString `http://example.com/out.js`;
        const compiledContent = 'console.log("compiled");';
        const sourceContent = 'console.log("source");';
        // 1. Create network UISourceCode for compiled script (Script)
        const { uiSourceCode: networkScript } = createContentProviderUISourceCode({
            url,
            mimeType: 'text/javascript',
            content: compiledContent,
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-script-project',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, compiledContent.length),
            universe: backend.universe,
        });
        // 2. Create network UISourceCode for clashing source (SourceMapScript)
        const { uiSourceCodes: [networkSource], } = createContentProviderUISourceCodes({
            items: [
                {
                    url,
                    mimeType: 'text/javascript',
                    content: sourceContent,
                    resourceType: Common.ResourceType.resourceTypes.SourceMapScript,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, sourceContent.length),
                },
            ],
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'sourcemap-project',
            universe: backend.universe,
        });
        const bindings = [];
        const persistence = backend.universe.persistence;
        const bindingCreatedPromise = new Promise(resolve => {
            persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
                bindings.push(event.data);
                if (bindings.length === 2) {
                    resolve();
                }
            });
        });
        // 3. Create filesystem UISourceCodes
        // Path `/var/www/out.js` (compiled)
        const { uiSourceCode: fileSystemScript } = createFileSystemUISourceCode({
            url: urlString `file:///var/www/out.js`,
            content: compiledContent,
            fileSystemPath: 'file:///var/www',
            mimeType: 'text/javascript',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, compiledContent.length),
            autoMapping: true,
            universe: backend.universe,
        });
        // Path `/var/www/src/out.js` (source)
        const { uiSourceCode: fileSystemSource } = createFileSystemUISourceCode({
            url: urlString `file:///var/www/src/out.js`,
            content: sourceContent,
            fileSystemPath: 'file:///var/www',
            mimeType: 'text/javascript',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, sourceContent.length),
            autoMapping: true,
            universe: backend.universe,
        });
        await bindingCreatedPromise;
        assert.lengthOf(bindings, 2);
        const scriptBinding = bindings.find(b => b.network === networkScript);
        assert.exists(scriptBinding);
        assert.strictEqual(scriptBinding?.fileSystem, fileSystemScript);
        const sourceBinding = bindings.find(b => b.network === networkSource);
        assert.exists(sourceBinding);
        assert.strictEqual(sourceBinding?.fileSystem, fileSystemSource);
    });
    it('is able to map ambiguous resources based on the selected project folder', async () => {
        const resetCssContent = '* { margin: 0 }';
        const jqueryJsContent = 'window.superb = 1;';
        const logo1Content = 'AAAA';
        const logo2Content = 'BBBBBBBB';
        // 1. Create network UISourceCodes
        const { uiSourceCodes: networkSourceCodes } = createContentProviderUISourceCodes({
            items: [
                {
                    url: urlString `http://example.com/reset.css`,
                    mimeType: 'text/css',
                    content: resetCssContent,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, resetCssContent.length),
                },
                {
                    url: urlString `http://example.com/jquery.js`,
                    mimeType: 'text/javascript',
                    content: jqueryJsContent,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, jqueryJsContent.length),
                },
                {
                    url: urlString `http://example.com/logo.png`,
                    mimeType: 'image/png',
                    content: logo2Content,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, logo2Content.length),
                },
            ],
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-project',
            universe: backend.universe,
        });
        const networkResetCss = networkSourceCodes[0];
        const networkJqueryJs = networkSourceCodes[1];
        const networkLogo = networkSourceCodes[2];
        const bindings = [];
        const persistence = backend.universe.persistence;
        const bindingsCreatedPromise = new Promise(resolve => {
            persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
                bindings.push(event.data);
                if (bindings.length === 3) {
                    resolve();
                }
            });
        });
        class MyTestFileSystem extends Persistence.FileSystemWorkspaceBinding.FileSystem {
            contentMap = new Map();
            constructor(fileSystemPath) {
                const isolatedFileSystemManager = backend.universe.isolatedFileSystemManager;
                const fileSystemWorkspaceBinding = new Persistence.FileSystemWorkspaceBinding.FileSystemWorkspaceBinding(isolatedFileSystemManager, backend.universe.workspace);
                class MyTestPlatformFileSystem extends Persistence.PlatformFileSystem.PlatformFileSystem {
                    constructor() {
                        super(urlString `${fileSystemPath}`, Persistence.PlatformFileSystem.PlatformFileSystemType.WORKSPACE_PROJECT, false);
                    }
                    supportsAutomapping() {
                        return true;
                    }
                }
                super(fileSystemWorkspaceBinding, new MyTestPlatformFileSystem(), backend.universe.workspace);
            }
            requestFileContent(uiSourceCode) {
                return Promise.resolve(new TextUtils.ContentData.ContentData(this.contentMap.get(uiSourceCode) || '', false, 'text/plain'));
            }
            requestMetadata(uiSourceCode) {
                const length = (this.contentMap.get(uiSourceCode) || '').length;
                return Promise.resolve(new Workspace.UISourceCode.UISourceCodeMetadata(null, length));
            }
            addFileToMap(url, content, mimeType) {
                const uiSourceCode = this.createUISourceCode(urlString `${url}`, Common.ResourceType.ResourceType.fromMimeType(mimeType));
                this.contentMap.set(uiSourceCode, content);
                this.addUISourceCode(uiSourceCode);
                return uiSourceCode;
            }
        }
        // 2. Create filesystem UISourceCodes for proj1
        const fsProj1 = new MyTestFileSystem('file:///var/www/code/proj1');
        fsProj1.addFileToMap('file:///var/www/code/proj1/reset.css', resetCssContent, 'text/css');
        fsProj1.addFileToMap('file:///var/www/code/proj1/jquery.js', jqueryJsContent, 'text/javascript');
        fsProj1.addFileToMap('file:///var/www/code/proj1/logo.png', logo1Content, 'image/png');
        // 3. Create filesystem UISourceCodes for proj2
        const fsProj2 = new MyTestFileSystem('file:///var/www/code/proj2');
        const fsResetCss2 = fsProj2.addFileToMap('file:///var/www/code/proj2/reset.css', resetCssContent, 'text/css');
        const fsJqueryJs2 = fsProj2.addFileToMap('file:///var/www/code/proj2/jquery.js', jqueryJsContent, 'text/javascript');
        const fsLogo2 = fsProj2.addFileToMap('file:///var/www/code/proj2/logo.png', logo2Content, 'image/png');
        await bindingsCreatedPromise;
        assert.lengthOf(bindings, 3);
        const logoBinding = bindings.find(b => b.network === networkLogo);
        assert.exists(logoBinding);
        assert.strictEqual(logoBinding?.fileSystem, fsLogo2);
        const resetCssBinding = bindings.find(b => b.network === networkResetCss);
        assert.exists(resetCssBinding);
        assert.strictEqual(resetCssBinding?.fileSystem, fsResetCss2);
        const jqueryJsBinding = bindings.find(b => b.network === networkJqueryJs);
        assert.exists(jqueryJsBinding);
        assert.strictEqual(jqueryJsBinding?.fileSystem, fsJqueryJs2);
    });
    it('verify that automapping is sane', async () => {
        const clock = sinon.useFakeTimers({ toFake: ['setTimeout'] });
        try {
            const timestamp = new Date('December 1, 1989');
            const bazContent = 'alert(1);';
            // Network resources
            const networkResources = [
                {
                    url: urlString `http://example.com`,
                    mimeType: 'text/html',
                    content: '<body>this is main resource</body>',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, null),
                },
                {
                    url: urlString `http://example.com/path/foo.js`,
                    mimeType: 'text/javascript',
                    content: 'console.log(\'foo.js!\');',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
                },
                {
                    url: urlString `http://example.com/bar.css?12341234`,
                    mimeType: 'text/css',
                    content: '* { box-sizing: border-box }',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, null),
                },
                {
                    url: urlString `http://example.com/baz.js`,
                    mimeType: 'text/javascript',
                    content: bazContent,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(new Date('December 3, 1989'), null),
                },
                {
                    url: urlString `http://example.com/images/image.png`,
                    mimeType: 'image/png',
                    content: '012345',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, 6),
                },
                {
                    url: urlString `http://example.com/elements/module.json`,
                    mimeType: 'application/json',
                    content: 'module descriptor 1',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
                },
                {
                    url: urlString `http://example.com/sources/module.json`,
                    mimeType: 'application/json',
                    content: 'module descriptor 2',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
                },
            ];
            const { uiSourceCodes: networkUISourceCodes } = createContentProviderUISourceCodes({
                items: networkResources,
                projectType: Workspace.Workspace.projectTypes.Network,
                projectId: 'network-project',
                universe: backend.universe,
            });
            const [networkIndexHtml, networkFooJs, networkBarCss, networkBazJs, networkImagePng, networkElementsJson, networkSourcesJson,] = networkUISourceCodes;
            const bindings = [];
            const persistence = backend.universe.persistence;
            persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
                bindings.push(event.data);
            });
            // Filesystem resources
            const fileSystemResources = [
                {
                    url: urlString `file:///var/www/index.html`,
                    mimeType: 'text/html',
                    content: '<body>this is main resource</body>',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, null),
                },
                {
                    url: urlString `file:///var/www/scripts/foo.js`,
                    mimeType: 'text/javascript',
                    content: 'console.log(\'foo.js!\');',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
                },
                {
                    url: urlString `file:///var/www/styles/bar.css`,
                    mimeType: 'text/css',
                    content: '* { box-sizing: border-box }',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, null),
                },
                {
                    url: urlString `file:///var/www/scripts/baz.js`,
                    mimeType: 'text/javascript',
                    content: bazContent,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(new Date('December 4, 1989'), null),
                },
                {
                    url: urlString `file:///var/www/images/image.png`,
                    mimeType: 'image/png',
                    content: '0123456789',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, 10),
                },
                {
                    url: urlString `file:///var/www/modules/elements/module.json`,
                    mimeType: 'application/json',
                    content: 'module descriptor 1',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
                },
                {
                    url: urlString `file:///var/www/modules/sources/module.json`,
                    mimeType: 'application/json',
                    content: 'module descriptor 2',
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
                },
            ];
            const fileSystemUISourceCodes = fileSystemResources.map(resource => {
                return createFileSystemUISourceCode({
                    url: resource.url,
                    mimeType: resource.mimeType,
                    content: resource.content,
                    fileSystemPath: 'file:///var/www',
                    metadata: resource.metadata,
                    autoMapping: true,
                    universe: backend.universe,
                })
                    .uiSourceCode;
            });
            const fileSystemIndexHtml = fileSystemUISourceCodes[0];
            const fileSystemFooJs = fileSystemUISourceCodes[1];
            const fileSystemBarCss = fileSystemUISourceCodes[2];
            const fileSystemElementsJson = fileSystemUISourceCodes[5];
            const fileSystemSourcesJson = fileSystemUISourceCodes[6];
            await clock.tickAsync(200);
            assert.lengthOf(bindings, 5);
            const indexHtmlBinding = persistence.binding(networkIndexHtml);
            assert.exists(indexHtmlBinding);
            assert.strictEqual(indexHtmlBinding?.fileSystem, fileSystemIndexHtml);
            const fooBinding = persistence.binding(networkFooJs);
            assert.exists(fooBinding);
            assert.strictEqual(fooBinding?.fileSystem, fileSystemFooJs);
            const barBinding = persistence.binding(networkBarCss);
            assert.exists(barBinding);
            assert.strictEqual(barBinding?.fileSystem, fileSystemBarCss);
            const elementsBinding = persistence.binding(networkElementsJson);
            assert.exists(elementsBinding);
            assert.strictEqual(elementsBinding?.fileSystem, fileSystemElementsJson);
            const sourcesBinding = persistence.binding(networkSourcesJson);
            assert.exists(sourcesBinding);
            assert.strictEqual(sourcesBinding?.fileSystem, fileSystemSourcesJson);
            assert.isNull(persistence.binding(networkBazJs));
            assert.isNull(persistence.binding(networkImagePng));
        }
        finally {
            clock.restore();
        }
    });
    it('correctly maps and demaps resources as they come and go', async () => {
        const url = urlString `http://example.com/path/foo.js`;
        const fileURL = urlString `file:///var/www/scripts/foo.js`;
        const content = 'console.log(\'foo.js!\');';
        const time = new Date('December 1, 1989');
        const persistence = backend.universe.persistence;
        const bindings = [];
        const removedBindings = [];
        persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
            bindings.push(event.data);
        });
        persistence.addEventListener(Persistence.Persistence.Events.BindingRemoved, event => {
            removedBindings.push(event.data);
        });
        // 1. Add a network resource.
        const { project: networkProject, uiSourceCode: networkSourceCode } = createContentProviderUISourceCode({
            url,
            mimeType: 'text/javascript',
            content,
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-project',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(time, content.length),
            universe: backend.universe,
        });
        // Wait a bit to ensure no binding is created (since the file system is missing).
        await new Promise(resolve => setTimeout(resolve, 50));
        assert.lengthOf(bindings, 0);
        // 2. Add a file system resource.
        const { uiSourceCode: fileSystemSourceCode, project: fileSystemProject } = createFileSystemUISourceCode({
            url: fileURL,
            content,
            fileSystemPath: 'file:///var/www',
            mimeType: 'text/javascript',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(time, content.length),
            autoMapping: true,
            universe: backend.universe,
        });
        const waitForBinding = (bindingList, length) => {
            return new Promise(resolve => {
                const check = () => {
                    if (bindingList.length === length) {
                        resolve();
                    }
                    else {
                        setTimeout(check, 10);
                    }
                };
                check();
            });
        };
        // Wait for binding to be created.
        await waitForBinding(bindings, 1);
        assert.strictEqual(bindings[0].network, networkSourceCode);
        assert.strictEqual(bindings[0].fileSystem, fileSystemSourceCode);
        // 3. Remove the network resource.
        networkProject.removeUISourceCode(url);
        // Wait for binding to be removed.
        await waitForBinding(removedBindings, 1);
        assert.strictEqual(removedBindings[0].network, networkSourceCode);
        assert.strictEqual(removedBindings[0].fileSystem, fileSystemSourceCode);
        // 4. Re-add the network resource.
        const newNetworkSourceCode = networkProject.createUISourceCode(url, Common.ResourceType.resourceTypes.Script);
        const contentProvider = TextUtils.StaticContentProvider.StaticContentProvider.fromString(url, Common.ResourceType.resourceTypes.Script, content);
        const metadata = new Workspace.UISourceCode.UISourceCodeMetadata(time, content.length);
        networkProject.addUISourceCodeWithProvider(newNetworkSourceCode, contentProvider, metadata, 'text/javascript');
        // Wait for binding to be created again.
        await waitForBinding(bindings, 2);
        assert.strictEqual(bindings[1].network, newNetworkSourceCode);
        assert.strictEqual(bindings[1].fileSystem, fileSystemSourceCode);
        // 5. Remove the file system.
        fileSystemProject.dispose();
        // Wait for binding to be removed again.
        await waitForBinding(removedBindings, 2);
        assert.strictEqual(removedBindings[1].network, newNetworkSourceCode);
        assert.strictEqual(removedBindings[1].fileSystem, fileSystemSourceCode);
    });
    it('correctly maps file:// URLs with special characters (URL-encoded paths)', async () => {
        const content = 'console.log(\'foo.js!\');';
        // Create network resources.
        const { uiSourceCodes: networkScriptCodes } = createContentProviderUISourceCodes({
            items: [
                {
                    url: urlString `file:///usr/local/node/script%201.js`,
                    mimeType: 'text/javascript',
                    content,
                },
                {
                    url: urlString `file:///usr/local/node/script%25201.js`,
                    mimeType: 'text/javascript',
                    content,
                },
            ],
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-project',
            universe: backend.universe,
        });
        const networkScript1 = networkScriptCodes[0];
        const networkScript2 = networkScriptCodes[1];
        const bindings = [];
        const persistence = backend.universe.persistence;
        const bindingCreatedPromise = new Promise(resolve => {
            persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
                bindings.push(event.data);
                if (bindings.length === 2) {
                    resolve();
                }
            });
        });
        // Create the first file system resource with a URL-encoded path.
        const { uiSourceCode: fileSystemScript1 } = createFileSystemUISourceCode({
            url: urlString `file:///usr/local/node/script%201.js`,
            content,
            fileSystemPath: 'file:///usr/local/node',
            mimeType: 'text/javascript',
            autoMapping: true,
            universe: backend.universe,
        });
        // Create the second file system resource with a double URL-encoded path.
        const { uiSourceCode: fileSystemScript2 } = createFileSystemUISourceCode({
            url: urlString `file:///usr/local/node/script%25201.js`,
            content,
            fileSystemPath: 'file:///usr/local/node',
            mimeType: 'text/javascript',
            autoMapping: true,
            universe: backend.universe,
        });
        await bindingCreatedPromise;
        assert.lengthOf(bindings, 2);
        const binding1 = bindings.find(b => b.network === networkScript1);
        assert.exists(binding1);
        assert.strictEqual(binding1?.fileSystem, fileSystemScript1);
        const binding2 = bindings.find(b => b.network === networkScript2);
        assert.exists(binding2);
        assert.strictEqual(binding2?.fileSystem, fileSystemScript2);
    });
    it('is capable of mapping file:// urls', async () => {
        const url = urlString `file:///usr/local/node/app.js`;
        const content = 'console.log(\'foo.js!\');';
        // 1. Create network UISourceCode
        const { uiSourceCode: networkScript } = createContentProviderUISourceCode({
            url,
            mimeType: 'text/javascript',
            content,
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-script-project',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
            universe: backend.universe,
        });
        const bindings = [];
        const persistence = backend.universe.persistence;
        const bindingCreatedPromise = new Promise(resolve => {
            persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
                bindings.push(event.data);
                resolve();
            });
        });
        // 2. Create filesystem UISourceCode
        const { uiSourceCode: fileSystemScript } = createFileSystemUISourceCode({
            url,
            content,
            fileSystemPath: 'file:///usr/local/node',
            mimeType: 'text/javascript',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
            autoMapping: true,
            universe: backend.universe,
        });
        await bindingCreatedPromise;
        assert.lengthOf(bindings, 1);
        const scriptBinding = bindings[0];
        assert.strictEqual(scriptBinding.network, networkScript);
        assert.strictEqual(scriptBinding.fileSystem, fileSystemScript);
    });
    // Replaces web test: http/tests/devtools/persistence/automapping-bind-dirty-network-sourcecode.js
    it('binds dirty network uiSourceCodes to filesystem', async () => {
        const url = urlString `http://127.0.0.1:8000/devtools/persistence/resources/foo.js`;
        const fileURL = urlString `file:///var/www/devtools/persistence/resources/foo.js`;
        const content = 'window.foo = ()=>\'foo\';';
        const modifiedContent = 'window.bar = ()=>\'bar\';';
        const persistence = backend.universe.persistence;
        const bindings = [];
        persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
            bindings.push(event.data);
        });
        // 1. Add a network resource.
        const { uiSourceCode: networkSourceCode } = createContentProviderUISourceCode({
            url,
            mimeType: 'text/javascript',
            content,
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-project',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, content.length),
            universe: backend.universe,
        });
        // Make the network resource dirty.
        networkSourceCode.setWorkingCopy(modifiedContent);
        // 2. Add a file system resource.
        const { uiSourceCode: fileSystemSourceCode } = createFileSystemUISourceCode({
            url: fileURL,
            content,
            fileSystemPath: 'file:///var/www',
            mimeType: 'text/javascript',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, content.length),
            autoMapping: true,
            universe: backend.universe,
        });
        const waitForBinding = (bindingList, length) => {
            return new Promise(resolve => {
                const check = () => {
                    if (bindingList.length === length) {
                        resolve();
                    }
                    else {
                        setTimeout(check, 10);
                    }
                };
                check();
            });
        };
        // Wait for binding to be created.
        await waitForBinding(bindings, 1);
        assert.strictEqual(bindings[0].network, networkSourceCode);
        assert.strictEqual(bindings[0].fileSystem, fileSystemSourceCode);
        // Verify file system is dirty and its working copy is updated.
        assert.isTrue(bindings[0].fileSystem.isDirty());
        assert.strictEqual(bindings[0].fileSystem.workingCopy(), modifiedContent);
    });
    it('verify that dirty fileSystem uiSourceCodes are bound to network', async () => {
        const clock = sinon.useFakeTimers({ toFake: ['setTimeout'] });
        try {
            const url = urlString `http://127.0.0.1:8000/devtools/persistence/resources/foo.js`;
            const fileURL = urlString `file:///var/www/devtools/persistence/resources/foo.js`;
            const content = '\n\nwindow.foo = ()=>\'foo\';\n';
            const dirtyContent = '\n\nwindow.foo = ()=>\'bar\';\n';
            const persistence = backend.universe.persistence;
            const bindings = [];
            persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
                bindings.push(event.data);
            });
            // 1. Create the filesystem UISourceCode.
            const { uiSourceCode: fileSystemSourceCode } = createFileSystemUISourceCode({
                url: fileURL,
                content,
                fileSystemPath: 'file:///var/www',
                mimeType: 'text/javascript',
                metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, content.length),
                autoMapping: true,
                universe: backend.universe,
            });
            // 2. Make the filesystem UISourceCode dirty.
            fileSystemSourceCode.setWorkingCopy(dirtyContent);
            assert.isTrue(fileSystemSourceCode.isDirty());
            // 3. Create the network UISourceCode.
            const { uiSourceCode: networkSourceCode } = createContentProviderUISourceCode({
                url,
                mimeType: 'text/javascript',
                content,
                projectType: Workspace.Workspace.projectTypes.Network,
                metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, content.length),
                universe: backend.universe,
            });
            await clock.tickAsync(200);
            assert.lengthOf(bindings, 1);
            assert.strictEqual(bindings[0].network, networkSourceCode);
            assert.strictEqual(bindings[0].fileSystem, fileSystemSourceCode);
        }
        finally {
            clock.restore();
        }
    });
    it('correctly maps and demaps resources as they are renamed', async () => {
        const url = urlString `http://example.com/path/foo.js`;
        const fileURL = urlString `file:///var/www/scripts/foo.js`;
        const content = 'console.log(\'foo.js!\');';
        const time = new Date('December 1, 1989');
        const persistence = backend.universe.persistence;
        const bindings = [];
        const removedBindings = [];
        persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
            bindings.push(event.data);
        });
        persistence.addEventListener(Persistence.Persistence.Events.BindingRemoved, event => {
            removedBindings.push(event.data);
        });
        // 1. Add a network resource.
        const { uiSourceCode: networkSourceCode } = createContentProviderUISourceCode({
            url,
            mimeType: 'text/javascript',
            content,
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-project',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(time, content.length),
            universe: backend.universe,
        });
        // 2. Add a file system resource.
        const { uiSourceCode: fileSystemSourceCode, project: fileSystemProject } = createFileSystemUISourceCode({
            url: fileURL,
            content,
            fileSystemPath: 'file:///var/www',
            mimeType: 'text/javascript',
            metadata: new Workspace.UISourceCode.UISourceCodeMetadata(time, content.length),
            autoMapping: true,
            universe: backend.universe,
        });
        const waitForBinding = (bindingList, length) => {
            return new Promise(resolve => {
                const check = () => {
                    if (bindingList.length === length) {
                        resolve();
                    }
                    else {
                        setTimeout(check, 10);
                    }
                };
                check();
            });
        };
        // Wait for binding to be created.
        await waitForBinding(bindings, 1);
        assert.strictEqual(bindings[0].network, networkSourceCode);
        assert.strictEqual(bindings[0].fileSystem, fileSystemSourceCode);
        // Stub renameFile on the platform file system to succeed.
        const platformFileSystem = fileSystemProject.fileSystem();
        const renameStub = sinon.stub(platformFileSystem, 'renameFile').callsFake((path, newName, callback) => {
            callback(true, newName);
        });
        const contentTypeStub = sinon.stub(platformFileSystem, 'contentType').returns(Common.ResourceType.resourceTypes.Script);
        // 3. Rename the file system resource to bar.js.
        const newName = 'bar.js';
        const renameSuccess = await fileSystemSourceCode.rename(newName);
        assert.isTrue(renameSuccess);
        // Wait for binding to be removed.
        await waitForBinding(removedBindings, 1);
        assert.strictEqual(removedBindings[0].network, networkSourceCode);
        assert.strictEqual(removedBindings[0].fileSystem, fileSystemSourceCode);
        assert.strictEqual(fileSystemSourceCode.url(), urlString `file:///var/www/scripts/bar.js`);
        // 4. Rename it back to foo.js.
        const oldName = 'foo.js';
        const renameSuccess2 = await fileSystemSourceCode.rename(oldName);
        assert.isTrue(renameSuccess2);
        // Wait for binding to be created again.
        await waitForBinding(bindings, 2);
        assert.strictEqual(bindings[1].network, networkSourceCode);
        assert.strictEqual(bindings[1].fileSystem, fileSystemSourceCode);
        assert.strictEqual(fileSystemSourceCode.url(), fileURL);
        renameStub.restore();
        contentTypeStub.restore();
    });
    it('correctly maps sourcemap sources with non-exact match', async () => {
        const cssContent = 'body { color: red; }';
        const scssContent = '$color: red; body { color: $color; }';
        class MultiFileTestPlatformFileSystem extends Persistence.PlatformFileSystem.PlatformFileSystem {
            #autoMapping;
            #files = new Set();
            constructor(path, autoMapping) {
                super(path, Persistence.PlatformFileSystem.PlatformFileSystemType.WORKSPACE_PROJECT, false);
                this.#autoMapping = autoMapping;
            }
            addFile(url) {
                this.#files.add(url);
            }
            supportsAutomapping() {
                return this.#autoMapping;
            }
            mimeFromPath(path) {
                const ext = Common.ParsedURL.ParsedURL.extractExtension(path);
                return Common.ResourceType.mimeTypeByExtension.get(ext) || 'text/plain';
            }
            searchInPath(_query, _progress) {
                return Promise.resolve([...this.#files]);
            }
        }
        class MultiFileTestFileSystem extends Persistence.FileSystemWorkspaceBinding.FileSystem {
            #files = new Map();
            addMockFile(url, content, metadata) {
                this.#files.set(url, { content, metadata });
            }
            requestFileContent(uiSourceCode) {
                const file = this.#files.get(uiSourceCode.url());
                if (!file) {
                    return Promise.resolve({ error: 'File not found' });
                }
                return Promise.resolve(new TextUtils.ContentData.ContentData(file.content, 
                /* isBase64 */ false, 'text/plain'));
            }
            requestMetadata(uiSourceCode) {
                const file = this.#files.get(uiSourceCode.url());
                return Promise.resolve(file ? file.metadata : null);
            }
        }
        // 1. Create network project with both CSS and SCSS files
        const { uiSourceCodes: [networkCSS, networkSCSS], } = createContentProviderUISourceCodes({
            items: [
                {
                    url: urlString `http://example.com/assets/s.css`,
                    mimeType: 'text/css',
                    content: cssContent,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, cssContent.length),
                },
                {
                    url: urlString `http://example.com/assets/s.scss`,
                    mimeType: 'text/x-scss',
                    content: scssContent,
                    resourceType: Common.ResourceType.resourceTypes.SourceMapStyleSheet,
                    metadata: new Workspace.UISourceCode.UISourceCodeMetadata(null, null),
                },
            ],
            projectType: Workspace.Workspace.projectTypes.Network,
            projectId: 'network-project',
            universe: backend.universe,
        });
        const bindings = [];
        const persistence = backend.universe.persistence;
        const bindingCreatedPromise = new Promise(resolve => {
            persistence.addEventListener(Persistence.Persistence.Events.BindingCreated, event => {
                bindings.push(event.data);
                if (bindings.length === 2) {
                    resolve();
                }
            });
        });
        const timestamp = new Date('December 1, 1989');
        // 2. Create filesystem project with both CSS and SCSS files
        const workspace = backend.universe.workspace;
        const isolatedFileSystemManager = backend.universe.isolatedFileSystemManager;
        const fileSystemWorkspaceBinding = new Persistence.FileSystemWorkspaceBinding.FileSystemWorkspaceBinding(isolatedFileSystemManager, workspace);
        const fileSystemPath = urlString `file:///var/www`;
        const platformFileSystem = new MultiFileTestPlatformFileSystem(fileSystemPath, true);
        const project = new MultiFileTestFileSystem(fileSystemWorkspaceBinding, platformFileSystem, workspace);
        // Add dist/s.css
        const cssUrl = urlString `file:///var/www/dist/s.css`;
        platformFileSystem.addFile(cssUrl);
        const cssMetadata = new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, cssContent.length);
        project.addMockFile(cssUrl, cssContent, cssMetadata);
        const fileSystemCSS = project.createUISourceCode(cssUrl, Common.ResourceType.ResourceType.fromMimeType('text/css'));
        project.addUISourceCode(fileSystemCSS);
        // Add src/s.scss
        const scssUrl = urlString `file:///var/www/src/s.scss`;
        platformFileSystem.addFile(scssUrl);
        const scssMetadata = new Workspace.UISourceCode.UISourceCodeMetadata(timestamp, scssContent.length);
        project.addMockFile(scssUrl, scssContent, scssMetadata);
        const fileSystemSCSS = project.createUISourceCode(scssUrl, Common.ResourceType.ResourceType.fromMimeType('text/x-scss'));
        project.addUISourceCode(fileSystemSCSS);
        await bindingCreatedPromise;
        assert.lengthOf(bindings, 2);
        const cssBinding = bindings.find(b => b.network === networkCSS);
        assert.exists(cssBinding);
        assert.strictEqual(cssBinding?.fileSystem, fileSystemCSS);
        const scssBinding = bindings.find(b => b.network === networkSCSS);
        assert.exists(scssBinding);
        assert.strictEqual(scssBinding?.fileSystem, fileSystemSCSS);
    });
});
//# sourceMappingURL=Automapping.test.js.map