Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat - pick and omit in result helper methods #102

Merged
merged 6 commits into from
Oct 26, 2023
Merged
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
23 changes: 20 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "npm run check && node esbuild.config.mjs production",
"check": "svelte-check --tsconfig tsconfig.json",
"test": "jest",
"test-w": "jest --watch",
"test-w": "jest --watch --no-cache",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
Expand All @@ -20,6 +20,7 @@
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"@unsplash/ts-namespace-import-plugin": "^1.0.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"esbuild-svelte": "^0.8.0",
Expand All @@ -31,11 +32,12 @@
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"tslib": "2.4.0",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"yaml": "^2.3.3"
},
"dependencies": {
"fp-ts": "^2.16.1",
"fuse.js": "^6.6.2",
"valibot": "^0.19.0"
}
}
}
8 changes: 4 additions & 4 deletions src/API.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { App } from "obsidian";

import { MigrationError, type FormDefinition, type FormOptions } from "./core/formDefinition";
import FormResult from "./FormResult";
import FormResult from "./core/FormResult";
import { exampleModalDefinition } from "./exampleModalDefinition";
import ModalFormPlugin from "./main";
import { ModalFormError } from "./utils/Error";
Expand Down Expand Up @@ -49,9 +49,9 @@ export class API {
getFormByName(name: string): FormDefinition | undefined {
const form = this.plugin.settings?.formDefinitions.find((form) => form.name === name);
if (form instanceof MigrationError) {
log_notice('🚫 The form you tried to load has an invalid format',
`The form "${name}" has an invalid format.`+
`We tried to automatically convert it but it failed, please fix it manually in the forms manager.
log_notice('🚫 The form you tried to load has an invalid format',
`The form "${name}" has an invalid format.` +
`We tried to automatically convert it but it failed, please fix it manually in the forms manager.
`)
return undefined;
} else {
Expand Down
10 changes: 5 additions & 5 deletions src/FormModal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { App, Modal, Platform, Setting } from "obsidian";
import MultiSelect from "./views/components/MultiSelect.svelte";
import FormResult, { formDataFromFormOptions, type ModalFormData } from "./FormResult";
import FormResult, { formDataFromFormOptions, type ModalFormData } from "./core/FormResult";
import { exhaustiveGuard } from "./safety";
import { get_tfiles_from_folder } from "./utils/files";
import type { FormDefinition, FormOptions } from "./core/formDefinition";
Expand Down Expand Up @@ -39,7 +39,7 @@ export class FormModal extends Modal {
fieldBase.setClass('modal-form-textarea')
return fieldBase.addTextArea((textEl) => {
if (typeof initialValue === 'string') { textEl.setValue(initialValue); }
textEl.onChange(value => {
textEl.onChange((value) => {
this.formResult[definition.name] = value;
});
textEl.inputEl.rows = 6;
Expand Down Expand Up @@ -133,7 +133,7 @@ export class FormModal extends Modal {
this.formResult[definition.name] = this.formResult[definition.name] || []
const options = fieldInput.source == 'fixed'
? fieldInput.multi_select_options
: get_tfiles_from_folder(fieldInput.folder, this.app).map(file => file.basename);
: get_tfiles_from_folder(fieldInput.folder, this.app).map((file) => file.basename);
this.svelteComponents.push(new MultiSelect({
target: fieldBase.controlEl,
props: {
Expand Down Expand Up @@ -205,7 +205,7 @@ export class FormModal extends Modal {
return exhaustiveGuard(type);
}
});

const submit = () => {
this.onSubmit(new FormResult(this.formResult, "ok"));
this.close();
Expand All @@ -230,7 +230,7 @@ export class FormModal extends Modal {

onClose() {
const { contentEl } = this;
this.svelteComponents.forEach(component => component.$destroy())
this.svelteComponents.forEach((component) => component.$destroy())
contentEl.empty();
this.formResult = {};
}
Expand Down
156 changes: 156 additions & 0 deletions src/core/FormResult.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
jest.mock("obsidian");
import FormResult, { ModalFormData } from "./FormResult";

describe("FormResult", () => {
const formData: ModalFormData = {
name: "John Doe",
age: 30,
hobbies: ["reading", "swimming"],
isEmployed: true,
};

describe("constructor", () => {
it("should create a new FormResult instance with the provided data and status", () => {
const result = new FormResult(formData, "ok");
expect(result).toBeInstanceOf(FormResult);
expect(result.getData()).toEqual(formData);
expect(result.status).toEqual("ok");
});
});

describe("asFrontmatterString", () => {
it("should return the data as a YAML frontmatter string", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name: John Doe
age: 30
hobbies:
- reading
- swimming
isEmployed: true
`;
expect(result.asFrontmatterString()).toEqual(expectedOutput);
});
});

describe("asDataviewProperties", () => {
it("should return the data as a string of dataview properties", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name:: John Doe
age:: 30
hobbies:: "reading","swimming"
isEmployed:: true`;
expect(result.asDataviewProperties()).toEqual(expectedOutput);
});
});

describe("getData", () => {
it("should return a copy of the data contained in the FormResult instance", () => {
const result = new FormResult(formData, "ok");
const dataCopy = result.getData();
expect(dataCopy).toEqual(formData);
expect(dataCopy).not.toBe(formData);
});
});

describe("asString", () => {
it("should return the data formatted as a string matching the provided template", () => {
const result = new FormResult(formData, "ok");
const template = "My name is {{name}}, and I am {{age}} years old.";
const expectedOutput =
"My name is John Doe, and I am 30 years old.";
expect(result.asString(template)).toEqual(expectedOutput);
});
});
describe("asDataviewProperties pick/omit", () => {
it("should return the data as a string of dataview properties with only the specified keys using options.pick", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name:: John Doe
age:: 30`;
expect(
result.asDataviewProperties({ pick: ["name", "age"] })
).toEqual(expectedOutput);
});

it("should return the data as a string of dataview properties with all keys except the specified ones using options.omit", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name:: John Doe
age:: 30`;
expect(
result.asDataviewProperties({ omit: ["hobbies", "isEmployed"] })
).toEqual(expectedOutput);
});

it("should return the data as a string of dataview properties with only the specified keys using options.pick and ignoring options.omit", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name:: John Doe
age:: 30`;
expect(
result.asDataviewProperties({
pick: ["name", "age"],
omit: ["hobbies", "isEmployed"],
})
).toEqual(expectedOutput);
});

it("should return the data as a string of dataview properties with all keys except the specified ones using options.omit and ignoring options.pick", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name:: John Doe
age:: 30`;
expect(
result.asDataviewProperties({
omit: ["hobbies", "isEmployed"],
pick: ["name", "age"],
})
).toEqual(expectedOutput);
});
});
describe("asFrontmatterString pick/omit", () => {
it("should return the data as a YAML frontmatter string with only the specified keys using options.pick", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name: John Doe
age: 30`;
expect(
result.asFrontmatterString({ pick: ["name", "age"] }).trim()
).toEqual(expectedOutput);
});

it("should return the data as a YAML frontmatter string with all keys except the specified ones using options.omit", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name: John Doe
age: 30`;
expect(
result
.asFrontmatterString({ omit: ["hobbies", "isEmployed"] })
.trim()
).toEqual(expectedOutput);
});

it("should return the data as a YAML frontmatter string with only the specified keys using options.pick and ignoring options.omit", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name: John Doe
age: 30`;
expect(
result
.asFrontmatterString({
pick: ["name", "age"],
omit: ["hobbies", "isEmployed"],
})
.trim()
).toEqual(expectedOutput);
});

it("should return the data as a YAML frontmatter string with all keys except the specified ones using options.omit and ignoring options.pick", () => {
const result = new FormResult(formData, "ok");
const expectedOutput = `name: John Doe
age: 30`;
expect(
result
.asFrontmatterString({
omit: ["hobbies", "isEmployed"],
pick: ["name", "age"],
})
.trim()
).toEqual(expectedOutput);
});
});
});
33 changes: 26 additions & 7 deletions src/FormResult.ts → src/core/FormResult.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { objectSelect } from './objectSelect';
import { stringifyYaml } from "obsidian";
import { log_error } from "./utils/Log";
import { ModalFormError } from "./utils/Error";
import { log_error } from "../utils/Log";
import { ModalFormError } from "../utils/Error";

type ResultStatus = "ok" | "cancelled";

Expand All @@ -15,6 +16,7 @@ function isPrimitiveArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(isPrimitive)
}


export function formDataFromFormOptions(values: Record<string, unknown>) {
const result: ModalFormData = {};
const invalidKeys = []
Expand All @@ -35,16 +37,33 @@ export function formDataFromFormOptions(values: Record<string, unknown>) {

export default class FormResult {
constructor(private data: ModalFormData, public status: ResultStatus) { }
asFrontmatterString() {
return stringifyYaml(this.data);
/**
* Transform the current data into a frontmatter string, which is expected
* to be enclosed in `---` when used in a markdown file.
* This method does not add the enclosing `---` to the string,
* so you can put it anywhere inside the frontmatter.
* @param {Object} [options] an options object describing what options to pick or omit
* @param {string[]} [options.pick] an array of key names to pick from the data
* @param {string[]} [options.omit] an array of key names to omit from the data
* @returns the data formatted as a frontmatter string
*/
asFrontmatterString(options?: unknown) {
const data = objectSelect(this.data, options)
return stringifyYaml(data);
}
/**
* Return the current data as a block of dataview properties
* @param {Object} [options] an options object describing what options to pick or omit
* @param {string[]} [options.pick] an array of key names to pick from the data
* @param {string[]} [options.omit] an array of key names to omit from the data
* @returns string
*/
asDataviewProperties(): string {
return Object.entries(this.data)
.map(([key, value]) => `${key}:: ${value}`)
asDataviewProperties(options?: unknown): string {
const data = objectSelect(this.data, options)
return Object.entries(data)
.map(([key, value]) =>
`${key}:: ${Array.isArray(value) ? value.map((v) => JSON.stringify(v)) : value}`
)
.join("\n");
}
/**
Expand Down
4 changes: 4 additions & 0 deletions src/core/__mocks__/obsidian.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { stringify } from 'yaml'
export function stringifyYaml(data: unknown) {
return stringify(data)
}
Loading