diff --git a/samples/notebooks/polyglot/Azure logs.ipynb b/samples/notebooks/polyglot/Azure logs.ipynb index dde5d5f40f..c779d7d447 100644 --- a/samples/notebooks/polyglot/Azure logs.ipynb +++ b/samples/notebooks/polyglot/Azure logs.ipynb @@ -2,7 +2,14 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "powershell" + }, + "polyglot_notebook": { + "kernelName": "powershell" + } + }, "source": [ "[this doc on github](https://github.com/dotnet/interactive/tree/main/samples/notebooks/polyglot)\n", "\n" @@ -14,6 +21,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -27,6 +37,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -40,6 +53,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -53,6 +69,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -66,6 +85,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -79,6 +101,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -94,6 +119,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -120,6 +148,9 @@ "metadata": { "dotnet_interactive": { "language": "pwsh" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -140,9 +171,9 @@ ], "metadata": { "kernelspec": { - "display_name": ".NET (PowerShell)", - "language": "PowerShell", - "name": ".net-powershell" + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" }, "language_info": { "file_extension": ".ps1", @@ -150,8 +181,90 @@ "name": "PowerShell", "pygments_lexer": "powershell", "version": "7.0" + }, + "polyglot_notebook": { + "kernelInfo": { + "defaultKernelName": "powershell", + "items": [ + { + "aliases": [], + "languageName": null, + "name": ".NET" + }, + { + "aliases": [ + "C#", + "c#" + ], + "languageName": "C#", + "name": "csharp" + }, + { + "aliases": [ + "F#", + "f#" + ], + "languageName": "F#", + "name": "fsharp" + }, + { + "aliases": [], + "languageName": "HTML", + "name": "html" + }, + { + "aliases": [ + "js" + ], + "languageName": "JavaScript", + "name": "javascript" + }, + { + "aliases": [], + "languageName": "KQL", + "name": "kql" + }, + { + "aliases": [], + "languageName": "Mermaid", + "name": "mermaid" + }, + { + "aliases": [], + "name": "powershell" + }, + { + "aliases": [ + "powershell" + ], + "languageName": "PowerShell", + "name": "pwsh" + }, + { + "aliases": [], + "languageName": "SQL", + "name": "sql" + }, + { + "aliases": [], + "languageName": null, + "name": "value" + }, + { + "aliases": [ + "frontend" + ], + "languageName": null, + "name": "vscode" + }, + { + "aliases": [], + "name": "webview" + } + ] + } } }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/samples/notebooks/polyglot/COVID-19-fsharp.dib b/samples/notebooks/polyglot/COVID-19-fsharp.dib index 3248778367..d6ca0290be 100644 --- a/samples/notebooks/polyglot/COVID-19-fsharp.dib +++ b/samples/notebooks/polyglot/COVID-19-fsharp.dib @@ -1,6 +1,6 @@ #!meta -{"kernelInfo":{"defaultKernelName":"csharp","items":[{"name":"csharp","aliases":["c#","C#"],"languageName":"C#"},{"name":".NET","aliases":[]},{"name":"fsharp","aliases":["f#","F#"],"languageName":"F#"},{"name":"html","aliases":[],"languageName":"HTML"},{"name":"kql","aliases":[],"languageName":"KQL"},{"name":"mermaid","aliases":[],"languageName":"Mermaid"},{"name":"pwsh","aliases":["powershell"],"languageName":"PowerShell"},{"name":"sql","aliases":[],"languageName":"SQL"},{"name":"value","aliases":[]},{"name":"vscode","languageName":null,"aliases":["frontend"]}]}} +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":["F#","f#"],"languageName":"F#","name":"fsharp"},{"aliases":[],"languageName":"HTML","name":"html"},{"aliases":[],"languageName":"Mermaid","name":"mermaid"},{"aliases":["powershell"],"languageName":"PowerShell","name":"pwsh"},{"aliases":[],"name":"value"}]}} #!markdown diff --git a/samples/notebooks/polyglot/COVID-19.ipynb b/samples/notebooks/polyglot/COVID-19.ipynb index 570f76605b..e52ccad1a6 100644 --- a/samples/notebooks/polyglot/COVID-19.ipynb +++ b/samples/notebooks/polyglot/COVID-19.ipynb @@ -386,20 +386,16 @@ "items": [ { "aliases": [ - "c#", - "C#" + "C#", + "c#" ], "languageName": "C#", "name": "csharp" }, - { - "aliases": [], - "name": ".NET" - }, { "aliases": [ - "f#", - "F#" + "F#", + "f#" ], "languageName": "F#", "name": "fsharp" @@ -434,12 +430,6 @@ { "aliases": [], "name": "value" - }, - { - "aliases": [ - "frontend" - ], - "name": "vscode" } ] } diff --git a/samples/notebooks/polyglot/Variable sharing.ipynb b/samples/notebooks/polyglot/Variable sharing.ipynb index 57447fc534..230799aa37 100644 --- a/samples/notebooks/polyglot/Variable sharing.ipynb +++ b/samples/notebooks/polyglot/Variable sharing.ipynb @@ -2,7 +2,14 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "[this doc on github](https://github.com/dotnet/interactive/tree/main/samples/notebooks/csharp/Docs)\n", "\n", @@ -17,6 +24,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -32,6 +42,9 @@ "metadata": { "dotnet_interactive": { "language": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -43,7 +56,14 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "Variables are shared by reference for reference types. A consequence of this is that if you share a mutable object, changes to its state will be visible across subkernels:" ] @@ -54,6 +74,9 @@ "metadata": { "dotnet_interactive": { "language": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -72,6 +95,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -90,6 +116,9 @@ "metadata": { "dotnet_interactive": { "language": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -101,7 +130,14 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "# Direct data entry with `#!value`\n", "\n", @@ -120,6 +156,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -138,6 +177,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -151,7 +193,14 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "\n", "### 2. From a file\n", @@ -165,6 +214,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -178,6 +230,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -188,7 +243,14 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "\n", "### 3. From a URL\n", @@ -202,6 +264,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -215,6 +280,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -225,7 +293,14 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "## Specifying a MIME type\n", "\n", @@ -238,6 +313,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -256,14 +334,28 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "This also causes the value to be saved in your `.ipynb` file, something that would not otherwise happen.\n" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "\n", "This also effectively stores the value in your `.ipynb` file, something that would not otherwise happen.\n" @@ -271,7 +363,14 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "source": [ "## Limitations\n", "\n", @@ -284,6 +383,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -300,6 +402,9 @@ "metadata": { "dotnet_interactive": { "language": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -315,6 +420,9 @@ "metadata": { "dotnet_interactive": { "language": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" } }, "outputs": [], @@ -333,8 +441,73 @@ "name": "C#", "pygments_lexer": "csharp", "version": "8.0" + }, + "polyglot_notebook": { + "kernelInfo": { + "defaultKernelName": "csharp", + "items": [ + { + "aliases": [], + "name": ".NET" + }, + { + "aliases": [ + "C#", + "c#" + ], + "languageName": "C#", + "name": "csharp" + }, + { + "aliases": [ + "F#", + "f#" + ], + "languageName": "F#", + "name": "fsharp" + }, + { + "aliases": [], + "languageName": "HTML", + "name": "html" + }, + { + "aliases": [ + "js" + ], + "languageName": "JavaScript", + "name": "javascript" + }, + { + "aliases": [], + "languageName": "KQL", + "name": "kql" + }, + { + "aliases": [], + "languageName": "Mermaid", + "name": "mermaid" + }, + { + "aliases": [ + "powershell" + ], + "languageName": "PowerShell", + "name": "pwsh" + }, + { + "aliases": [], + "languageName": "SQL", + "name": "sql" + }, + { + "aliases": [], + "name": "value" + } + ] + } } }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/samples/notebooks/polyglot/d3js.ipynb b/samples/notebooks/polyglot/d3js.ipynb index b2d72985b3..f0165c91ff 100644 --- a/samples/notebooks/polyglot/d3js.ipynb +++ b/samples/notebooks/polyglot/d3js.ipynb @@ -211,22 +211,22 @@ "kernelInfo": { "defaultKernelName": "csharp", "items": [ + { + "aliases": [], + "name": ".NET" + }, { "aliases": [ - "c#", - "C#" + "C#", + "c#" ], "languageName": "C#", "name": "csharp" }, - { - "aliases": [], - "name": ".NET" - }, { "aliases": [ - "f#", - "F#" + "F#", + "f#" ], "languageName": "F#", "name": "fsharp" @@ -236,6 +236,13 @@ "languageName": "HTML", "name": "html" }, + { + "aliases": [ + "js" + ], + "languageName": "JavaScript", + "name": "javascript" + }, { "aliases": [], "languageName": "KQL", @@ -267,6 +274,10 @@ "frontend" ], "name": "vscode" + }, + { + "aliases": [], + "name": "webview" } ] } diff --git a/samples/notebooks/polyglot/github repo milestone report.ipynb b/samples/notebooks/polyglot/github repo milestone report.ipynb index ac45c86370..02d14e4016 100644 --- a/samples/notebooks/polyglot/github repo milestone report.ipynb +++ b/samples/notebooks/polyglot/github repo milestone report.ipynb @@ -664,7 +664,61 @@ "items": [ { "aliases": [], + "name": ".NET" + }, + { + "aliases": [ + "C#", + "c#" + ], + "languageName": "C#", "name": "csharp" + }, + { + "aliases": [ + "F#", + "f#" + ], + "languageName": "F#", + "name": "fsharp" + }, + { + "aliases": [], + "languageName": "HTML", + "name": "html" + }, + { + "aliases": [ + "js" + ], + "languageName": "JavaScript", + "name": "javascript" + }, + { + "aliases": [], + "languageName": "KQL", + "name": "kql" + }, + { + "aliases": [], + "languageName": "Mermaid", + "name": "mermaid" + }, + { + "aliases": [ + "powershell" + ], + "languageName": "PowerShell", + "name": "pwsh" + }, + { + "aliases": [], + "languageName": "SQL", + "name": "sql" + }, + { + "aliases": [], + "name": "value" } ] } diff --git a/samples/notebooks/polyglot/polyglot telemetry.dib b/samples/notebooks/polyglot/polyglot telemetry.dib index 643b031268..e18fd39c5a 100644 --- a/samples/notebooks/polyglot/polyglot telemetry.dib +++ b/samples/notebooks/polyglot/polyglot telemetry.dib @@ -1,6 +1,6 @@ #!meta -{"kernelInfo":{"defaultKernelName":"csharp","items":[{"name":"csharp","aliases":[]}]}} +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":["F#","f#"],"languageName":"F#","name":"fsharp"},{"aliases":[],"languageName":"HTML","name":"html"},{"aliases":[],"languageName":"KQL","name":"kql-telemetry"},{"aliases":[],"languageName":"Mermaid","name":"mermaid"},{"aliases":["powershell"],"languageName":"PowerShell","name":"pwsh"},{"aliases":[],"name":"value"}]}} #!csharp diff --git a/src/polyglot-notebooks-vscode-common/src/commands.ts b/src/polyglot-notebooks-vscode-common/src/commands.ts index b93fa33503..a6afdbdb0a 100644 --- a/src/polyglot-notebooks-vscode-common/src/commands.ts +++ b/src/polyglot-notebooks-vscode-common/src/commands.ts @@ -327,7 +327,7 @@ export function registerFileCommands(context: vscode.ExtensionContext, parserSer }; const createForIpynb = viewType === constants.JupyterViewType; - const rawNotebookMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, createForIpynb); + const rawNotebookMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, {}, createForIpynb); const content = new vscode.NotebookData([cell]); content.metadata = rawNotebookMetadata; const notebook = await vscode.workspace.openNotebookDocument(viewType, content); diff --git a/src/polyglot-notebooks-vscode-common/src/documentSemanticTokenProvider.ts b/src/polyglot-notebooks-vscode-common/src/documentSemanticTokenProvider.ts index 3b337448d6..558ae26c6a 100644 --- a/src/polyglot-notebooks-vscode-common/src/documentSemanticTokenProvider.ts +++ b/src/polyglot-notebooks-vscode-common/src/documentSemanticTokenProvider.ts @@ -84,7 +84,7 @@ export class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTo if (notebook) { const isIpynb = metadataUtilities.isIpynbNotebook(notebook); const bareMetadata = metadataUtilities.createDefaultNotebookDocumentMetadata(); - const rawBareMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(bareMetadata, isIpynb); + const rawBareMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(bareMetadata, notebook.metadata, isIpynb); const _succeeded = await vscodeNotebookManagement.replaceNotebookMetadata(notebook.uri, rawBareMetadata); const kernelInfos = metadataUtilities.getKernelInfosFromNotebookDocument(notebook); this.dynamicTokenProvider.rebuildNotebookGrammar(notebook.uri, kernelInfos, true); diff --git a/src/polyglot-notebooks-vscode-common/src/metadataUtilities.ts b/src/polyglot-notebooks-vscode-common/src/metadataUtilities.ts index d8585cb27c..dfe753d168 100644 --- a/src/polyglot-notebooks-vscode-common/src/metadataUtilities.ts +++ b/src/polyglot-notebooks-vscode-common/src/metadataUtilities.ts @@ -107,6 +107,7 @@ export function getNotebookDocumentMetadataFromInteractiveDocument(interactiveDo } notebookMetadata.kernelInfo.items = notebookMetadata.kernelInfo.items.map(item => ensureProperShapeForDocumentKernelInfo(item)); + cleanupMedata(notebookMetadata); return notebookMetadata; } @@ -154,14 +155,15 @@ export function getNotebookDocumentMetadataFromNotebookDocument(document: vscode } notebookMetadata.kernelInfo.items = notebookMetadata.kernelInfo.items.map(item => ensureProperShapeForDocumentKernelInfo(item)); + cleanupMedata(notebookMetadata); return notebookMetadata; } export function getNotebookDocumentMetadataFromCompositeKernel(kernel: CompositeKernel): NotebookDocumentMetadata { const notebookMetadata = createDefaultNotebookDocumentMetadata(); notebookMetadata.kernelInfo.defaultKernelName = kernel.defaultKernelName ?? notebookMetadata.kernelInfo.defaultKernelName; - notebookMetadata.kernelInfo.items = kernel.childKernels.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map(k => ({ name: k.name, aliases: k.kernelInfo.aliases, languageName: k.kernelInfo.languageName })); - + notebookMetadata.kernelInfo.items = kernel.childKernels.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).filter(k => k.supportsCommand(contracts.SubmitCodeType)).map(k => ({ name: k.name, aliases: k.kernelInfo.aliases, languageName: k.kernelInfo.languageName })); + cleanupMedata(notebookMetadata); return notebookMetadata; } @@ -170,6 +172,10 @@ function ensureProperShapeForDocumentKernelInfo(kernelInfo: contracts.DocumentKe kernelInfo.aliases = []; } + if (kernelInfo.languageName === undefined) { + delete kernelInfo.languageName; + } + return kernelInfo; } @@ -223,6 +229,12 @@ export function getKernelInfosFromNotebookDocument(notebookDocument: vscodeLike. supportedKernelCommands: [], supportedDirectives: [] })); + + kernelInfos.forEach(ki => { + if (ki.languageName === undefined || ki.languageName === null) { + delete ki["languageName"]; + } + }); return kernelInfos; } @@ -286,7 +298,7 @@ export function getRawInteractiveDocumentMetadataFromNotebookDocumentMetadata(no return notebookDocumentMetadata; } -export function getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata: NotebookDocumentMetadata, createForIpynb: boolean): { [key: string]: any } { +export function getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata: NotebookDocumentMetadata, documentRawMetadata: { [key: string]: any }, createForIpynb: boolean): { [key: string]: any } { const rawMetadata: { [key: string]: any } = {}; if (createForIpynb) { @@ -301,16 +313,147 @@ export function getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(noteb rawMetadata.polyglot_notebook = notebookDocumentMetadata; } + sortAndMerge(rawMetadata, documentRawMetadata); return rawMetadata; } +export function sortAndMerge(destination: { [key: string]: any }, source: { [key: string]: any }) { + + if (destination === null) { + return; + } + if (source === null) { + if (destination !== null) { + sortInPlace(destination); + } + } + else { + sortInPlace(destination);//? + sortInPlace(source);//? + + const sourceKeys = Object.keys(source);//? + for (const key of sourceKeys) { + key;//? + destination[key];//? + if (destination[key] === undefined) { + destination; + destination[key] = source[key]; + + } else { + if (source[key] !== undefined && source[key] !== null) { + if (Array.isArray(destination[key])) { + mergeArray(destination[key], source[key]); + } else if (typeof destination[key] === 'object') { + sortAndMerge(destination[key], source[key]); + } + } + } + } + } + + destination;//? +} + +function mergeArray(destination: any[], source: any[]) { + source;//? + for (let i = 0; i < source.length; i++) { + let srcValue = source[i]; + if (srcValue !== null) { + srcValue;//? + if (isKernelInfo(srcValue)) { + const found = destination.find(e => srcValue.localName.localeCompare(e.localName) === 0); + if (found) { + sortAndMerge(found, srcValue); + } else { + destination.push(srcValue); + } + } else if (isDocumentKernelInfo(srcValue)) { + const found = destination.find(e => srcValue.name.localeCompare(e.name) === 0); + if (found) { + found;//? + srcValue;//? + sortAndMerge(found, srcValue); + } else { + destination.push(srcValue); + } + } else { + const found = destination.find(e => e === srcValue); + if (found) { + sortAndMerge(found, srcValue); + } else { + destination.push(srcValue); + } + } + destination;//? + } + } +} + +function isKernelInfo(k: any): k is contracts.KernelInfo { + return k.localName !== undefined; +} + +function isDocumentKernelInfo(k: any): k is contracts.DocumentKernelInfo { + return k.name !== undefined; +} + +export function sortInPlace(value: any): any { + if (value === null) { + return value; + } + else if (value === undefined) { + return value; + } + else if (Array.isArray(value)) { + if (value.length > 0) { + for (let i = 0; i < value.length; i++) { + value[i] = sortInPlace(value[i]); + } + return value.sort((a, b) => { + if (isKernelInfo(a) && isKernelInfo(b)) { + return a.localName.localeCompare(b.localName); + } else if (isDocumentKernelInfo(a) && isDocumentKernelInfo(b)) { + return a.name.localeCompare(b.name); + } + else { + return a > b ? 1 : (a < b ? -1 : 0); + } + }); + } + else { + return value; + } + } else if (typeof value === 'object') { + const sourceKeys = Object.keys(value).sort(); + let sorted: { [key: string]: any } = {}; + for (const key of sourceKeys) { + const sourceValue = value[key]; + sorted[key] = sortInPlace(sourceValue); + if (isWritable(value, key)) { + delete value[key]; + value[key] = sorted[key]; + } + + } + + return value; + } else { + return value; + } +} + +function isWritable(obj: T, key: keyof T) { + const desc = Object.getOwnPropertyDescriptor(obj, key) || {}; + return Boolean(desc.writable); +} + export function mergeNotebookCellMetadata(baseMetadata: NotebookCellMetadata, metadataWithNewValues: NotebookCellMetadata): NotebookCellMetadata { const resultMetadata = { ...baseMetadata }; if (metadataWithNewValues.kernelName) { resultMetadata.kernelName = metadataWithNewValues.kernelName; } - return resultMetadata; + return sortInPlace(resultMetadata); } export function mergeNotebookDocumentMetadata(baseMetadata: NotebookDocumentMetadata, metadataWithNewValues: NotebookDocumentMetadata): NotebookDocumentMetadata { @@ -324,7 +467,8 @@ export function mergeNotebookDocumentMetadata(baseMetadata: NotebookDocumentMeta } resultMetadata.kernelInfo.items = [...kernelInfoItems.values()]; - return resultMetadata; + cleanupMedata(resultMetadata); + return sortInPlace(resultMetadata); } export function mergeRawMetadata(baseMetadata: { [key: string]: any }, metadataWithNewValues: { [key: string]: any }): { [key: string]: any } { @@ -333,7 +477,7 @@ export function mergeRawMetadata(baseMetadata: { [key: string]: any }, metadataW resultMetadata[key] = metadataWithNewValues[key]; } - return resultMetadata; + return sortInPlace(resultMetadata); } export function createDefaultNotebookDocumentMetadata(): NotebookDocumentMetadata { @@ -347,9 +491,65 @@ export function createDefaultNotebookDocumentMetadata(): NotebookDocumentMetadat } ], } - };; + }; } function createDefaultNotebookCellMetadata(): NotebookCellMetadata { return {}; -} \ No newline at end of file +} + +export function areEquivalentObjects(object1: { [key: string]: any }, object2: { [key: string]: any }, keysToIgnore?: Set): boolean { + sortInPlace(object1); + sortInPlace(object2); + + const isObject = (object: any) => { + return object !== null && typeof object === 'object'; + }; + + const object1Keys = Object.keys(object1).filter(k => !(keysToIgnore?.has(k))); + const object2Keys = Object.keys(object2).filter(k => !(keysToIgnore?.has(k))); + + if (object1Keys.length !== object2Keys.length) { + return false; + } + + for (const key of object1Keys) { + key;//? + const value1 = object1[key];//? + const value2 = object2[key];//? + const bothAreObjects = isObject(value1) && isObject(value2); //? + const bothAreArrays = Array.isArray(value1) && Array.isArray(value2); + + if (bothAreArrays) { + if (value1.length !== value2.length) {//? + return false; + } + for (let index = 0; index < value1.length; index++) { + const element1 = value1[index];//? + const element2 = value2[index];//? + if (!areEquivalentObjects(element1, element2)) { + return false; + } + } + } else if (bothAreObjects) { + const equivalent = areEquivalentObjects(value1, value2); + if (!equivalent) { + return false; + } + } else if (value1 !== value2) //? + { + value1;//? + value2;//? + return false; + } + } + return true; +} + +function cleanupMedata(notebookMetadata: NotebookDocumentMetadata) { + notebookMetadata.kernelInfo.items.forEach(ki => { + if (ki.languageName === undefined || ki.languageName === null) { + delete ki.languageName; + } + }); +} diff --git a/src/polyglot-notebooks-vscode-common/src/notebookControllers.ts b/src/polyglot-notebooks-vscode-common/src/notebookControllers.ts index fecbd115b9..f887e370e6 100644 --- a/src/polyglot-notebooks-vscode-common/src/notebookControllers.ts +++ b/src/polyglot-notebooks-vscode-common/src/notebookControllers.ts @@ -196,6 +196,14 @@ export class DotNetNotebookKernel { return client.execute(source, vscodeUtilities.getCellKernelName(cell), outputObserver, diagnosticObserver, { id: cell.document.uri.toString() }).then(async (success) => { await outputUpdatePromise; + + const isIpynb = metadataUtilities.isIpynbNotebook(cell.notebook); + const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(cell.notebook); + const kernelNotebokMetadata = metadataUtilities.getNotebookDocumentMetadataFromCompositeKernel(client.kernel); + const mergedMetadata = metadataUtilities.mergeNotebookDocumentMetadata(notebookDocumentMetadata, kernelNotebokMetadata); + const rawNotebookDocumentMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(mergedMetadata, cell.notebook.metadata, isIpynb); + + await vscodeNotebookManagement.replaceNotebookMetadata(cell.notebook.uri, rawNotebookDocumentMetadata); endExecution(client, cell, success); }).catch(async () => { await outputUpdatePromise; @@ -328,6 +336,7 @@ async function updateNotebookMetadata(notebook: vscode.NotebookDocument, clientM } } + async function updateKernelInfoMetadata(client: InteractiveClient, document: vscode.NotebookDocument): Promise { const isIpynb = metadataUtilities.isIpynbNotebook(document); client.channel.receiver.subscribe({ @@ -340,22 +349,29 @@ async function updateKernelInfoMetadata(client: InteractiveClient, document: vsc for (const item of notebookMetadata.kernelInfo.items) { if (item.name === kernelInfoProduced.kernelInfo.localName) { metadataChanged = true; - item.languageName = kernelInfoProduced.kernelInfo.languageName; + if (kernelInfoProduced.kernelInfo.languageName) { + item.languageName = kernelInfoProduced.kernelInfo.languageName; + } item.aliases = kernelInfoProduced.kernelInfo.aliases; } } if (!metadataChanged) { - // nothing changed, must be a new kernel - notebookMetadata.kernelInfo.items.push({ - name: kernelInfoProduced.kernelInfo.localName, - languageName: kernelInfoProduced.kernelInfo.languageName, - aliases: kernelInfoProduced.kernelInfo.aliases - }); + if (kernelInfoProduced.kernelInfo.supportedKernelCommands.find(ci => ci.name === contracts.SubmitCodeType)) { + const kernelInfo: contracts.DocumentKernelInfo = { + name: kernelInfoProduced.kernelInfo.localName, + aliases: kernelInfoProduced.kernelInfo.aliases + }; + if (kernelInfoProduced.kernelInfo.languageName !== undefined && kernelInfoProduced.kernelInfo.languageName !== null) { + kernelInfo.languageName = kernelInfoProduced.kernelInfo.languageName; + } + // nothing changed, must be a new kernel + notebookMetadata.kernelInfo.items.push(kernelInfo); + } } const existingRawNotebookDocumentMetadata = document.metadata; - const updatedRawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, isIpynb); + const updatedRawNotebookDocumentMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, existingRawNotebookDocumentMetadata, isIpynb); const newRawNotebookDocumentMetadata = metadataUtilities.mergeRawMetadata(existingRawNotebookDocumentMetadata, updatedRawNotebookDocumentMetadata); await vscodeNotebookManagement.replaceNotebookMetadata(document.uri, newRawNotebookDocumentMetadata); } @@ -365,7 +381,7 @@ async function updateKernelInfoMetadata(client: InteractiveClient, document: vsc const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); const kernelNotebokMetadata = metadataUtilities.getNotebookDocumentMetadataFromCompositeKernel(client.kernel); const mergedMetadata = metadataUtilities.mergeNotebookDocumentMetadata(notebookDocumentMetadata, kernelNotebokMetadata); - const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(mergedMetadata, isIpynb); + const rawNotebookDocumentMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(mergedMetadata, document.metadata, isIpynb); await vscodeNotebookManagement.replaceNotebookMetadata(document.uri, rawNotebookDocumentMetadata); } diff --git a/src/polyglot-notebooks-vscode-common/src/notebookSerializers.ts b/src/polyglot-notebooks-vscode-common/src/notebookSerializers.ts index 4bba31cdd8..22caa169a0 100644 --- a/src/polyglot-notebooks-vscode-common/src/notebookSerializers.ts +++ b/src/polyglot-notebooks-vscode-common/src/notebookSerializers.ts @@ -15,7 +15,7 @@ import * as constants from './constants'; function toInteractiveDocumentElement(cell: vscode.NotebookCellData): contracts.InteractiveDocumentElement { // just need to match the shape const fakeCell: vscodeLike.NotebookCell = { - kind: 0, + kind: vscodeLike.NotebookCellKind.Code, metadata: cell.metadata ?? {} }; const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(fakeCell); @@ -32,7 +32,7 @@ async function deserializeNotebookByType(parserServer: NotebookParserServer, ser const interactiveDocument = await parserServer.parseInteractiveDocument(serializationType, rawData); const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromInteractiveDocument(interactiveDocument); const createForIpynb = serializationType === contracts.DocumentSerializationType.Ipynb; - const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, createForIpynb); + const rawNotebookDocumentMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, {}, createForIpynb); const notebookData: vscode.NotebookData = { cells: interactiveDocument.elements.map(element => toVsCodeNotebookCellData(element)), metadata: rawNotebookDocumentMetadata @@ -70,7 +70,12 @@ export function createAndRegisterNotebookSerializers(context: vscode.ExtensionCo return serializeNotebookByType(parserServer, serializationType, eol, data); }, }; - const notebookSerializer = vscode.workspace.registerNotebookSerializer(notebookType, serializer); + + const notebookoptions: vscode.NotebookDocumentContentOptions = notebookType === contracts.DocumentSerializationType.Dib + ? { transientOutputs: true, transientDocumentMetadata: { custom: true }, transientCellMetadata: { custom: true } } + : {}; + + const notebookSerializer = vscode.workspace.registerNotebookSerializer(notebookType, serializer, notebookoptions); context.subscriptions.push(notebookSerializer); return serializer; }; diff --git a/src/polyglot-notebooks-vscode-common/src/vscodeNotebookManagement.ts b/src/polyglot-notebooks-vscode-common/src/vscodeNotebookManagement.ts index 09492f19e1..95ade5a09a 100644 --- a/src/polyglot-notebooks-vscode-common/src/vscodeNotebookManagement.ts +++ b/src/polyglot-notebooks-vscode-common/src/vscodeNotebookManagement.ts @@ -9,6 +9,7 @@ import { extractHostAndNomalize, isKernelCommandEnvelope, isKernelEventEnvelope, import * as rxjs from 'rxjs'; import * as connection from './polyglot-notebooks/connection'; import * as contracts from './polyglot-notebooks/contracts'; +import { areEquivalentObjects, isIpynbNotebook } from './metadataUtilities'; export function getNotebookDocumentFromEditor(notebookEditor: vscode.NotebookEditor): vscode.NotebookDocument { return notebookEditor.notebook; @@ -31,11 +32,26 @@ export async function replaceNotebookCellMetadata(notebookUri: vscode.Uri, cellI } export async function replaceNotebookMetadata(notebookUri: vscode.Uri, documentMetadata: { [key: string]: any }): Promise { - const notebookEdit = vscode.NotebookEdit.updateNotebookMetadata(documentMetadata); - const edit = new vscode.WorkspaceEdit(); - edit.set(notebookUri, [notebookEdit]); - const succeeded = await vscode.workspace.applyEdit(edit); - return succeeded; + const notebook = vscode.workspace.notebookDocuments.find(d => d.uri === notebookUri); + if (notebook) { + const metadata = notebook.metadata; + const keysToIngore = new Set(); + if (!isIpynbNotebook(notebook)) { + // dib format doesn't use the proeprty 'custom' so this should not be involved in the diff. + keysToIngore.add("custom"); + } + const shouldUpdate = !areEquivalentObjects(metadata, documentMetadata, keysToIngore); + + if (shouldUpdate) { + const notebookEdit = vscode.NotebookEdit.updateNotebookMetadata(documentMetadata); + const edit = new vscode.WorkspaceEdit(); + edit.set(notebookUri, [notebookEdit]); + const succeeded = await vscode.workspace.applyEdit(edit); + return succeeded; + } + return false; + } + return false; } export async function handleCustomInputRequest(prompt: string, inputTypeHint: string, password: boolean): Promise<{ handled: boolean, result: string | null | undefined }> { diff --git a/src/polyglot-notebooks-vscode-common/tests/metadataUtilities.test.ts b/src/polyglot-notebooks-vscode-common/tests/metadataUtilities.test.ts index 96e1d7c581..d3b16a0262 100644 --- a/src/polyglot-notebooks-vscode-common/tests/metadataUtilities.test.ts +++ b/src/polyglot-notebooks-vscode-common/tests/metadataUtilities.test.ts @@ -285,7 +285,6 @@ describe('metadata utility tests', async () => { displayName: 'unused', isComposite: false, isProxy: false, - languageName: undefined, localName: 'fsharp', supportedDirectives: [], supportedKernelCommands: [], @@ -339,7 +338,6 @@ describe('metadata utility tests', async () => { displayName: 'unused', isComposite: false, isProxy: false, - languageName: undefined, localName: 'fsharp', supportedDirectives: [], supportedKernelCommands: [], @@ -458,11 +456,14 @@ describe('metadata utility tests', async () => { it('notebook metadata can be extracted from a composite kernel', () => { const kernel = new CompositeKernel('composite'); const cs = new Kernel('csharp', 'csharp'); + cs.registerCommandHandler({ commandType: contracts.SubmitCodeType, handle: (_invocation) => Promise.resolve() }); cs.kernelInfo.aliases.push('cs'); kernel.add(cs); const fs = new Kernel('fsharp', 'fsharp'); + fs.registerCommandHandler({ commandType: contracts.SubmitCodeType, handle: (_invocation) => Promise.resolve() }); fs.kernelInfo.aliases.push('fs'); kernel.add(fs); + const value = new Kernel('value', 'value'); kernel.defaultKernelName = fs.name; const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromCompositeKernel(kernel); @@ -519,35 +520,28 @@ describe('metadata utility tests', async () => { kernelInfo: { defaultKernelName: 'fsharp', items: [ - { - name: 'csharp', - aliases: ['cs'], - languageName: 'csharp' - }, + { name: 'fsharp', aliases: ['fs'], languageName: 'fsharp' + }, + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' } ] } }; const interactiveDocumentMetadata = metadataUtilities.getRawInteractiveDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata); expect(interactiveDocumentMetadata).to.deep.equal({ - kernelInfo: { + kernelInfo: + { defaultKernelName: 'fsharp', - items: [ - { - name: 'csharp', - aliases: ['cs'], - languageName: 'csharp' - }, - { - name: 'fsharp', - aliases: ['fs'], - languageName: 'fsharp' - } - ] + items: + [{ aliases: ['fs'], languageName: 'fsharp', name: 'fsharp' }, + { aliases: ['cs'], languageName: 'csharp', name: 'csharp' }] } }); }); @@ -570,7 +564,7 @@ describe('metadata utility tests', async () => { ] } }; - const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, true); + const rawNotebookDocumentMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, {}, true); expect(rawNotebookDocumentMetadata).to.deep.equal({ custom: { metadata: { @@ -619,7 +613,7 @@ describe('metadata utility tests', async () => { ] } }; - const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, false); + const rawNotebookDocumentMetadata = metadataUtilities.getMergedRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, {}, false); expect(rawNotebookDocumentMetadata).to.deep.equal({ polyglot_notebook: { kernelInfo: { @@ -703,6 +697,227 @@ describe('metadata utility tests', async () => { }); }); + it("merges metadata correctly", () => { + let destination = { + "custom": { + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "polyglot_notebook": { + "kernelInfo": { + "defaultKernelName": "powershell", + "items": [ + { + "aliases": [ + ], + "languageName": null, + "name": ".NET" + }, + { + "aliases": [ + "C#", + "c#" + ], + "languageName": "C#", + "name": "csharp" + }, + { + "aliases": [ + "F#", + "f#" + ], + "languageName": "F#", + "name": "fsharp" + }, + { + "aliases": [ + ], + "languageName": "HTML", + "name": "html" + }, + { + "aliases": [ + "js" + ], + "languageName": "JavaScript", + "name": "javascript" + }, + { + "aliases": [ + ], + "languageName": "Mermaid", + "name": "mermaid" + }, + { + "aliases": [ + ], + "name": "powershell" + }, + { + "aliases": [ + "powershell" + ], + "languageName": "PowerShell", + "name": "pwsh" + }, + { + "aliases": [ + ], + "languageName": null, + "name": "value" + }, + { + "aliases": [ + "frontend" + ], + "languageName": null, + "name": "vscode" + }, + { + "aliases": [ + ], + "name": "webview" + } + ] + } + } + } + } + }; + + let source = { + "custom": { + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "polyglot_notebook": { + "kernelInfo": { + "defaultKernelName": "powershell", + "items": [ + { + "aliases": [ + "C#", + "c#" + ], + "languageName": "C#", + "name": "csharp" + }, + { + "aliases": [ + "F#", + "f#" + ], + "languageName": "F#", + "name": "fsharp" + }, + { + "aliases": [ + ], + "languageName": "HTML", + "name": "html" + }, + { + "aliases": [ + "js" + ], + "languageName": "JavaScript", + "name": "javascript" + }, + { + "aliases": [ + ], + "languageName": "Mermaid", + "name": "mermaid" + }, + { + "aliases": [ + ], + "name": "powershell" + }, + { + "aliases": [ + "powershell" + ], + "languageName": "PowerShell", + "name": "pwsh" + }, + { + "aliases": [ + ], + "languageName": null, + "name": "value" + }, + { + "aliases": [ + "frontend" + ], + "languageName": null, + "name": "vscode" + }, + { + "aliases": [ + ], + "name": "webview" + } + ] + } + } + }, + "cells": [ + ], + "nbformat": 4, + "nbformat_minor": 2 + }, + "indentAmount": " " + }; + + metadataUtilities.sortAndMerge(destination, source); + + expect(destination).to.deep.equal({ + custom: + { + cells: [], + metadata: + { + kernelspec: + { + display_name: '.NET (C#)', + language: 'C#', + name: '.net-csharp' + }, + polyglot_notebook: + { + kernelInfo: + { + defaultKernelName: 'powershell', + items: + [{ aliases: [], languageName: null, name: '.NET' }, + { aliases: ['C#', 'c#'], languageName: 'C#', name: 'csharp' }, + { aliases: ['F#', 'f#'], languageName: 'F#', name: 'fsharp' }, + { aliases: [], languageName: 'HTML', name: 'html' }, + { aliases: ['js'], languageName: 'JavaScript', name: 'javascript' }, + { aliases: [], languageName: 'Mermaid', name: 'mermaid' }, + { aliases: [], name: 'powershell' }, + { aliases: ['powershell'], languageName: 'PowerShell', name: 'pwsh' }, + { aliases: [], languageName: null, name: 'value' }, + { aliases: ['frontend'], languageName: null, name: 'vscode' }, + { aliases: [], name: 'webview' }] + } + } + }, + nbformat: 4, + nbformat_minor: 2 + }, + indentAmount: ' ' + }); + }); + it('raw metadata can be merged', () => { const baseMetadata: { [key: string]: any } = { custom: { @@ -745,5 +960,4 @@ describe('metadata utility tests', async () => { } }); }); - }); \ No newline at end of file diff --git a/src/polyglot-notebooks-vscode-common/tests/misc.test.ts b/src/polyglot-notebooks-vscode-common/tests/misc.test.ts index eab01843a8..032d7bf837 100644 --- a/src/polyglot-notebooks-vscode-common/tests/misc.test.ts +++ b/src/polyglot-notebooks-vscode-common/tests/misc.test.ts @@ -8,6 +8,7 @@ import { createUri, debounce, executeSafe, getVersionNumber, getWorkingDirectory import { decodeToString } from './utilities'; import * as vscodeLike from '../../src/vscode-common/interfaces/vscode-like'; +import { areEquivalentObjects, sortInPlace } from '../../src/vscode-common/metadataUtilities'; describe('Miscellaneous tests', () => { @@ -213,4 +214,174 @@ describe('Miscellaneous tests', () => { }); } }); + + describe('sorted objects', () => { + it('sorts object keys', () => { + const source = { + 'key2': "value", + 'key1': [1, 2, 3, 4] + }; + + const sorted = sortInPlace(source); + + expect(sorted).to.deep.equal({ + key1: [1, 2, 3, 4], + key2: 'value' + }); + }); + + it('sorts object recursively', () => { + const source = { + 'key2': "value", + 'key1': [1, 2, 3, 4], + 'key3': { + 'key2': "value", + 'key1': [1, 2, 3, 4], + } + }; + + const sorted = sortInPlace(source); + + expect(sorted).to.deep.equal({ + key1: [1, 2, 3, 4], + key2: 'value', + key3: { + key1: [1, 2, 3, 4], + key2: 'value' + } + }); + }); + + it('sorts object containing arrays', () => { + const source = { + 'key2': "value", + 'key1': [6, 2, 3, 4], + 'key3': { + 'key2': "value", + 'key1': [1, 5, 3, 4], + } + }; + + const sorted = sortInPlace(source); + + expect(sorted).to.deep.equal({ + key1: [2, 3, 4, 6], + key2: 'value', + key3: { + key1: [1, 3, 4, 5], + key2: 'value' + } + }); + }); + + it('sorts kernel infos by local name', () => { + const source = { + 'key2': "value", + 'key1': [6, 2, 3, 4], + 'key3': { + 'key2': "value", + 'key1': [{ localName: "d", displayName: "1" }, { localName: "a", displayName: "3" }, { localName: "b", displayName: "2" }], + } + }; + + const sorted = sortInPlace(source); + + expect(sorted).to.deep.equal({ + key1: [2, 3, 4, 6], + key2: 'value', + key3: + { + key1: + [ + { displayName: '3', localName: 'a' }, + { displayName: '2', localName: 'b' }, + { displayName: '1', localName: 'd' } + ], + key2: 'value' + } + }); + }); + }); + + describe('comparing objects', () => { + it('objects are equivalent regardles of key order', () => { + const data1 = { + 'key2': "value", + 'key1': [1, 2, 3, 4] + }; + + const data2 = { + 'key1': [1, 2, 3, 4], + 'key2': "value" + }; + + const d = areEquivalentObjects(data1, data2); //? + expect(d).to.be.true; + }); + + it('objects are equivalent regardles of array order', () => { + const data1 = { + 'key2': "value", + 'key1': [1, 2, 3, 4] + }; + + const data2 = { + 'key1': [1, 3, 2, 4], + 'key2': "value" + }; + + const d = areEquivalentObjects(data1, data2); //? + expect(d).to.be.true; + }); + + it('can ingore keys when checking if objects are equivalent', () => { + const data1 = { + 'key2': "value", + 'key1': [1, 2, 3, 4], + 'key5': "value" + }; + + const data2 = { + 'key1': [1, 3, 2, 4], + 'key2': "value", + 'key4': "value" + }; + + const ingoreKeys = new Set(); + ingoreKeys.add("key4"); + ingoreKeys.add("key5"); + const d = areEquivalentObjects(data1, data2, ingoreKeys); //? + expect(d).to.be.true; + }); + + it('objects are not equivalent if they have different data', () => { + const data1 = { + 'key2': "value", + 'key1': [1, 2, 3, 4] + }; + + const data2 = { + 'key1': [1, 2, 3, 4, 5], + 'key2': "value" + }; + + const d = areEquivalentObjects(data1, data2); //? + expect(d).to.be.false; + }); + + it('objects are not equivalent if they have different keys', () => { + const data1 = { + 'key2': "value", + 'key1': [1, 2, 3, 4] + }; + + const data2 = { + 'key12': [1, 2, 3, 4], + 'key22': "value" + }; + + const d = areEquivalentObjects(data1, data2); //? + expect(d).to.be.false; + }); + }); });