Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
registry=https://registry.npmjs.org/
registry=https://office.pkgs.visualstudio.com/_packaging/OfficeDev/npm/registry/
always-auth=true
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"--colors",
"${workspaceFolder}/test/end-to-end",
"-r",
"ts-node/register",
"${workspaceFolder}/test/register-ts-node.js",
"${workspaceFolder}/test/end-to-end/*.ts"
],
"internalConsoleOptions": "openOnSessionStart",
Expand All @@ -33,7 +33,7 @@
"--colors",
"${workspaceFolder}/test/unit",
"-r",
"ts-node/register",
"${workspaceFolder}/test/register-ts-node.js",
"${workspaceFolder}/test/unit/*.test.ts"
],
"internalConsoleOptions": "openOnSessionStart",
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Custom functions enable you to add new functions to Excel by defining those func

This repository contains the source code used by the [Yo Office generator](https://github.com/OfficeDev/generator-office) when you create a new custom functions project. You can also use this repository as a sample to base your own custom functions project from if you choose not to use the generator. For more detailed information about custom functions in Excel, see the [Custom functions overview](https://learn.microsoft.com/office/dev/add-ins/excel/custom-functions-overview) article in the Office Add-ins documentation or see the [additional resources](#additional-resources) section of this repository.

## npm registry auth
Run the following command to get the authenticator for the first time:

`npm install --global @microsoft/artifacts-npm-credprovider --registry https://pkgs.dev.azure.com/artifacts-public/23934c1b-a3b5-4b70-9dd3-d1bef4cc72a0/_packaging/AzureArtifacts/npm/registry/`
Comment thread
millerds marked this conversation as resolved.

Run `artifacts-npm-credprovider` to authenticate for the npm registry.

## Debugging custom functions

This template supports debugging custom functions from [Visual Studio Code](https://code.visualstudio.com/). For more information see [Custom functions debugging](https://aka.ms/custom-functions-debug). For general information on debugging task panes and other Office Add-in parts, see [Test and debug Office Add-ins](https://learn.microsoft.com/office/dev/add-ins/testing/test-debug-office-add-ins).
Expand Down
4,457 changes: 2,424 additions & 2,033 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 14 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"start": "office-addin-debugging start manifest.xml",
"stop": "office-addin-debugging stop manifest.xml",
"test": "npm run test:unit && npm run test:e2e",
"test:e2e": "mocha -r ts-node/register test/end-to-end/tests.ts",
"test:unit": "mocha -r ts-node/register test/unit/*.test.ts",
"test:e2e": "mocha -r ./test/register-ts-node.js test/end-to-end/tests.ts",
"test:unit": "mocha -r ./test/register-ts-node.js test/unit/*.test.ts",
"validate": "office-addin-manifest validate manifest.xml",
"watch": "webpack --mode development --watch"
},
Expand All @@ -46,31 +46,30 @@
"@types/office-runtime": "^1.0.35",
"acorn": "^8.5.0",
"babel-loader": "^10.1.1",
"copy-webpack-plugin": "^12.0.2",
"copy-webpack-plugin": "^14.0.0",
"custom-functions-metadata-plugin": "^2.1.2",
"eslint-plugin-office-addins": "^4.0.3",
"eslint-plugin-office-addins": "^4.0.10",
"file-loader": "^6.2.0",
"html-loader": "^5.0.0",
"html-webpack-plugin": "^5.6.0",
"mocha": "^11.7.4",
"office-addin-cli": "^2.0.6",
"office-addin-debugging": "^6.0.6",
"office-addin-dev-certs": "^2.0.6",
"office-addin-lint": "^3.0.6",
"office-addin-manifest": "^2.1.2",
"office-addin-mock": "^3.0.6",
"office-addin-prettier-config": "^2.0.1",
"office-addin-test-helpers": "^2.0.3",
"office-addin-test-server": "^2.0.3",
"os-browserify": "^0.3.0",
"office-addin-cli": "^2.0.10",
"office-addin-debugging": "^6.1.2",
"office-addin-dev-certs": "^2.0.10",
"office-addin-lint": "^3.0.10",
"office-addin-manifest": "^2.1.6",
"office-addin-mock": "^3.0.10",
"office-addin-prettier-config": "^2.0.5",
"office-addin-test-helpers": "^2.0.7",
"office-addin-test-server": "^2.0.7",
"process": "^0.11.10",
Comment thread
millerds marked this conversation as resolved.
"request": "^2.88.2",
"source-map-loader": "^5.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.2",
"webpack": "^5.105.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "5.2.5"
"webpack-dev-server": "^6.0.0"
},
"prettier": "office-addin-prettier-config",
"browserslist": [
Expand Down
25 changes: 14 additions & 11 deletions test/end-to-end/src/debugger-websocket.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import * as assert from "assert";
import { sleep } from "./test-helpers";
const WebSocket = require("ws");
const request = require("request");

/* global require, console */
let connectionOpened = false;
let messageId = 0;
const limitOfReconnectTries = 60;
let wsUrl: string | undefined;

function findUrl(jsonUrl: string): void {
let options = { json: true };

request(jsonUrl, options, (error, res, body) => {
if (!error && res.statusCode == 200) {
wsUrl = body[0].webSocketDebuggerUrl;
async function findUrl(jsonUrl: string): Promise<void> {
try {
const response = await fetch(jsonUrl, { signal: AbortSignal.timeout(1000) });
if (!response.ok) {
return;
}
});

const body = await response.json();
wsUrl = body?.[0]?.webSocketDebuggerUrl;
} catch {
// Debugger endpoint may not be ready yet. Retry loop handles this.
}
}

export async function connectToWebsocket(reconnectTry: number = 1): Promise<WebSocket | undefined> {
Expand All @@ -25,7 +28,7 @@ export async function connectToWebsocket(reconnectTry: number = 1): Promise<WebS

while (!wsUrl && reconnectTry < limitOfReconnectTries) {
console.log(`Attaching debugger to '${jsonUrl}'...`);
findUrl(jsonUrl);
await findUrl(jsonUrl);
reconnectTry++;
await sleep(1000);
}
Expand All @@ -39,12 +42,12 @@ export async function connectToWebsocket(reconnectTry: number = 1): Promise<WebS
connectionOpened = true;
return resolve(ws);
};
ws.onerror = (err) => {
ws.onerror = (err: any) => {
if (connectionOpened) {
assert.fail(`Websocket error: ${err.message}`);
}
};
ws.onmessage = (response) => {
ws.onmessage = (response: any) => {
assert.strictEqual(
JSON.parse(response.data).error,
undefined,
Expand Down
65 changes: 60 additions & 5 deletions test/end-to-end/src/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,69 @@ export async function closeWorkbook(): Promise<void> {
}
}

export function addTestResult(testValues: any[], resultName: string, resultValue: any, expectedValue: any) {
var data = {};
data["expectedValue"] = expectedValue;
data["resultName"] = resultName;
data["resultValue"] = resultValue;
export type TestResult = {
expectedValue: any;
name: string;
value: any;
};

export function addTestResult(testValues: any[], name: string, value: any, expectedValue: any) {
const data: TestResult = {
expectedValue,
name,
value,
};
testValues.push(data);
}

export function addErrorResult(testValues: any[], errorMessage: string) {
testValues.push({
name: "test-error",
value: errorMessage,
expectedValue: "no-error",
});
}

export function formatError(err: any): string {
if (!err) {
return "Unknown error (null/undefined)";
}

const parts: string[] = [];

if (err.message) {
parts.push(`Message: ${err.message}`);
} else {
parts.push(`${err}`);
}

if (err.code) {
parts.push(`Code: ${err.code}`);
}

if (err.debugInfo) {
if (err.debugInfo.code) {
parts.push(`DebugCode: ${err.debugInfo.code}`);
}
if (err.debugInfo.message) {
parts.push(`DebugMessage: ${err.debugInfo.message}`);
}
if (err.debugInfo.errorLocation) {
parts.push(`Location: ${err.debugInfo.errorLocation}`);
}
if (err.debugInfo.innerError) {
const inner = err.debugInfo.innerError;
parts.push(`InnerError: ${inner.code || ""} ${inner.message || JSON.stringify(inner)}`.trim());
}
}

if (err.stack) {
parts.push(`Stack: ${err.stack}`);
}

return parts.join(" | ");
}

export async function closeDesktopApplication(): Promise<boolean> {
const processName: string = "Excel";

Expand Down
85 changes: 45 additions & 40 deletions test/end-to-end/src/test-taskpane.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,74 @@
import functionsJsonData from "./test-data.json";
import { pingTestServer, sendTestResults } from "office-addin-test-helpers";
import { closeWorkbook, sleep } from "./test-helpers";
import { addErrorResult, closeWorkbook, formatError, sleep } from "./test-helpers";

/* global Office, document, Excel, run, navigator */
const customFunctionsData = (<any>functionsJsonData).functions;
const port: number = 4201;
let testValues = [];
let testValues: any[] = [];

Office.onReady(async () => {
document.getElementById("sideload-msg").style.display = "none";
document.getElementById("app-body").style.display = "flex";
document.getElementById("run").onclick = run;
document.getElementById("sideload-msg")!.style.display = "none";
document.getElementById("app-body")!.style.display = "flex";
document.getElementById("run")!.onclick = run;
addTestResult("UserAgent", navigator.userAgent);

const testServerResponse: object = await pingTestServer(port);
if (testServerResponse["status"] === 200) {
await runCfTests();
await sendTestResults(testValues, port);
await closeWorkbook();
try {
const testServerResponse = (await pingTestServer(port)) as { status?: number };
if (testServerResponse.status === 200) {
await runCfTests();
await sendTestResults(testValues, port);
await closeWorkbook();
} else {
addErrorResult(testValues, `Ping failed: ${JSON.stringify(testServerResponse)}`);
await sendTestResults(testValues, port).catch(() => {});
}
} catch (err) {
testValues = [];
addErrorResult(testValues, `Initialization failed: ${formatError(err)}`);
await sendTestResults(testValues, port).catch(() => {});
}
});

async function runCfTests(): Promise<void> {
// Exercise custom functions
await Excel.run(async (context) => {
for (let key in customFunctionsData) {
const formula: string = customFunctionsData[key].formula;
const range = context.workbook.getSelectedRange();
range.formulas = [[formula]];
await context.sync();
try {
await Excel.run(async (context) => {
for (let key in customFunctionsData) {
const formula: string = customFunctionsData[key].formula;
const range = context.workbook.getSelectedRange();
range.formulas = [[formula]];
await context.sync();

await sleep(5000);
await sleep(5000);

// Check to if this is a streaming function
await readCFData(key, customFunctionsData[key].streaming != undefined ? 2 : 1);
}
});
await readCFData(key, customFunctionsData[key].streaming !== undefined ? 2 : 1);
}
});
} catch (err) {
testValues = [];
addErrorResult(testValues, `runCfTests failed: ${formatError(err)}`);
throw err;
}
}

export async function readCFData(cfName: string, readCount: number): Promise<void> {
await Excel.run(async (context) => {
// if this is a streaming function, we want to capture two values so we can
// validate the function is indeed streaming
for (let i = 0; i < readCount; i++) {
try {
const range = context.workbook.getSelectedRange();
range.load("values");
await context.sync();
const range = context.workbook.getSelectedRange();
range.load("values");
await context.sync();

await sleep(5000);
await sleep(5000);

addTestResult(cfName, range.values[0][0]);
Promise.resolve();
} catch {
Promise.reject();
}
addTestResult(cfName, range.values[0][0]);
}
});
}

function addTestResult(resultName: string, resultValue: any) {
var data = {};
var nameKey = "Name";
var valueKey = "Value";
data[nameKey] = resultName;
data[valueKey] = resultValue;
function addTestResult(name: string, value: any) {
const data = {
name,
value,
};
testValues.push(data);
}
Loading