mirror of
https://github.com/groldsf/obsidian_check_plugin.git
synced 2026-07-22 05:37:48 +00:00
add tests
This commit is contained in:
parent
892ce8c482
commit
55f74a8e60
12 changed files with 6187 additions and 2479 deletions
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
|
|
@ -8,9 +8,32 @@ on:
|
|||
- 'manifest.json'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Upload test coverage report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage/
|
||||
|
||||
build:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -26,12 +49,12 @@ jobs:
|
|||
- name: Build plugin
|
||||
run: npm run build
|
||||
|
||||
- name: Create release
|
||||
- name: Create release folder
|
||||
run: |
|
||||
mkdir release
|
||||
cp main.js manifest.json styles.css release/
|
||||
|
||||
- name: Upload artifact
|
||||
- name: Upload built artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: obsidian-plugin
|
||||
|
|
@ -40,7 +63,6 @@ jobs:
|
|||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
|
|
|||
37
.github/workflows/test.yml
vendored
Normal file
37
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
name: Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16, 18, 20]
|
||||
steps:
|
||||
- name: Checkout репозитория
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Установка Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Установка зависимостей
|
||||
run: npm ci
|
||||
|
||||
- name: Запуск тестов
|
||||
run: npm test -- --coverage
|
||||
|
||||
- name: Сохранение отчёта о покрытии
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage/
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -20,3 +20,5 @@ data.json
|
|||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
coverage/
|
||||
|
|
|
|||
64
__tests__/findCheckboxesLine.test.ts
Normal file
64
__tests__/findCheckboxesLine.test.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { findCheckboxesLine, syncCheckboxes } from "../src/checkboxUtils";
|
||||
|
||||
describe("findCheckboxesLine", () => {
|
||||
test("распознаёт корректные строки с чекбоксами", () => {
|
||||
expect(findCheckboxesLine("- [ ] Task")).not.toBeNull();
|
||||
expect(findCheckboxesLine(" - [ ] Task")).not.toBeNull();
|
||||
expect(findCheckboxesLine("- [x] Task")).not.toBeNull();
|
||||
expect(findCheckboxesLine("* [ ] Another task")).not.toBeNull();
|
||||
expect(findCheckboxesLine("+ [x] Task")).not.toBeNull();
|
||||
expect(findCheckboxesLine("1. [ ] Numbered task")).not.toBeNull();
|
||||
expect(findCheckboxesLine("1. [x] Numbered task")).not.toBeNull();
|
||||
});
|
||||
|
||||
test("игнорирует некорректные строки", () => {
|
||||
// Отсутствует пробел после маркера списка
|
||||
expect(findCheckboxesLine("-[ ] Task")).toBeNull();
|
||||
expect(findCheckboxesLine("*[x] Task")).toBeNull();
|
||||
expect(findCheckboxesLine("1.[ ] Numbered task")).toBeNull();
|
||||
// Неверное содержимое скобок или неверный регистр
|
||||
expect(findCheckboxesLine("- [] Task")).toBeNull();
|
||||
expect(findCheckboxesLine("- [X] Task")).toBeNull();
|
||||
// Не соответствует шаблону чекбокса
|
||||
expect(findCheckboxesLine(" - Task")).toBeNull();
|
||||
expect(findCheckboxesLine("Just some text")).toBeNull();
|
||||
expect(findCheckboxesLine("1. Some numbered task")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("syncCheckboxes", () => {
|
||||
test("обновляет родительский чекбокс, если все дочерние отмечены", () => {
|
||||
const text = "- [ ] Parent\n - [x] Child 1\n - [x] Child 2";
|
||||
// Для строки "- [ ] Parent":
|
||||
// match[1] = "" (нулевой отступ), match[2] = "-" (1 символ)
|
||||
// => позиция чекбокса = 0 + 1 + 2 = 3
|
||||
expect(syncCheckboxes(text)).toEqual([{ line: 0, ch: 3, value: "x" }]);
|
||||
});
|
||||
|
||||
test("снимает отметку у родителя, если не все дочерние отмечены", () => {
|
||||
const text = "- [x] Parent\n - [x] Child 1\n - [ ] Child 2";
|
||||
// Родительский чекбокс должен стать незамеченным, то есть "x" -> " "
|
||||
expect(syncCheckboxes(text)).toEqual([{ line: 0, ch: 3, value: " " }]);
|
||||
});
|
||||
|
||||
test("не изменяет родителя, если у него нет дочерних элементов", () => {
|
||||
const text = "- [x] Single";
|
||||
expect(syncCheckboxes(text)).toEqual([]);
|
||||
});
|
||||
|
||||
test("тройная вложенность: обновляет только промежуточный уровень", () => {
|
||||
const text = "- [ ] Parent\n - [ ] Child\n - [x] Grandchild";
|
||||
// Для строки " - [ ] Child":
|
||||
// match[1] = " " (2 пробела), match[2] = "-" (1 символ)
|
||||
// => позиция чекбокса = 2 + 1 + 2 = 5
|
||||
expect(syncCheckboxes(text)).toEqual([{ line: 1, ch: 5, value: "x" }]);
|
||||
});
|
||||
|
||||
test("тройная вложенность: обновляет родительский чекбокс, если все вложенные отмечены", () => {
|
||||
const text = "- [ ] Parent\n - [x] Child\n - [x] Grandchild";
|
||||
// Здесь для строки " - [x] Child" изменений не требуется (она уже отмечена).
|
||||
// Родительский элемент имеет единственного дочернего, отмеченного как [x],
|
||||
// поэтому ожидается обновление строки Parent: позиция = 0 + 1 + 2 = 3.
|
||||
expect(syncCheckboxes(text)).toEqual([{ line: 0, ch: 3, value: "x" }]);
|
||||
});
|
||||
});
|
||||
|
|
@ -15,7 +15,7 @@ const context = await esbuild.context({
|
|||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
entryPoints: ["./src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
|
|
|
|||
38
jest.config.ts
Normal file
38
jest.config.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* For a detailed explanation regarding each configuration property, visit:
|
||||
* https://jestjs.io/docs/configuration
|
||||
*/
|
||||
|
||||
import type {Config} from 'jest';
|
||||
|
||||
const config: Config = {
|
||||
|
||||
// Automatically clear mock calls, instances, contexts and results before every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
collectCoverage: true,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
coverageDirectory: "coverage",
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: "v8",
|
||||
|
||||
// An array of file extensions your modules use
|
||||
moduleFileExtensions: [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
|
||||
preset: "ts-jest",
|
||||
|
||||
testEnvironment: "node",
|
||||
|
||||
};
|
||||
|
||||
export default config;
|
||||
55
main.ts
55
main.ts
|
|
@ -1,55 +0,0 @@
|
|||
import { Plugin, Editor } from "obsidian";
|
||||
|
||||
export default class CheckboxSyncPlugin extends Plugin {
|
||||
async onload() {
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("editor-change", (editor) => {
|
||||
this.updateParentCheckboxes(editor);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateParentCheckboxes(editor: Editor) {
|
||||
const lines = editor.getValue().split("\n");
|
||||
let updates: { line: number; ch: number; value: string }[] = [];
|
||||
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
// Регулярные выражения для всех типов списков (с учетом пробела перед чекбоксом)
|
||||
const match = lines[i].match(/^(\s*)([*+-]|\d+\.) \[([ x-])\] /);
|
||||
if (!match) continue;
|
||||
|
||||
const indent = match[1].length; // Индентирование
|
||||
const isChecked = match[3] === "x"; // Состояние чекбокса
|
||||
let allChildrenChecked = true;
|
||||
let hasChildren = false;
|
||||
let j = i + 1;
|
||||
|
||||
// Ищем вложенные элементы для всех типов списков
|
||||
while (j < lines.length) {
|
||||
const childMatch = lines[j].match(/^(\s*)([*+-]|\d+\.) \[([ x-])\] /);
|
||||
if (!childMatch || childMatch[1].length <= indent) break;
|
||||
hasChildren = true;
|
||||
if (childMatch[3] !== "x") allChildrenChecked = false;
|
||||
j++;
|
||||
}
|
||||
|
||||
// Обновляем родительский чекбокс, если есть дочерние
|
||||
if (hasChildren) {
|
||||
const checkboxPos = match[1].length + match[2].length + 2; // Позиция чекбокса
|
||||
if (allChildrenChecked && !isChecked) {
|
||||
updates.push({ line: i, ch: checkboxPos, value: "x" });
|
||||
} else if (!allChildrenChecked && isChecked) {
|
||||
updates.push({ line: i, ch: checkboxPos, value: " " });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Применяем изменения через replaceRange()
|
||||
if (updates.length > 0) {
|
||||
editor.blur();
|
||||
updates.forEach(({ line, ch, value }) => {
|
||||
editor.replaceRange(value, { line, ch }, { line, ch: ch + 1 });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
8315
package-lock.json
generated
8315
package-lock.json
generated
File diff suppressed because it is too large
Load diff
51
package.json
51
package.json
|
|
@ -1,24 +1,31 @@
|
|||
{
|
||||
"name": "checkbox-sync",
|
||||
"version": "1.0.0",
|
||||
"description": "Automatically checks the parent checkbox if all child checkboxes are completed, and unchecks it otherwise",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
"name": "checkbox-sync",
|
||||
"version": "1.0.0",
|
||||
"description": "Automatically checks the parent checkbox if all child checkboxes are completed, and unchecks it otherwise",
|
||||
"main": "main.js",
|
||||
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"prebuild": "npm test",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"jest": "^29.7.0",
|
||||
"obsidian": "^1.7.2",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
src/checkboxUtils.ts
Normal file
44
src/checkboxUtils.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Функция для поиска строк с чекбоксами в формате Markdown.
|
||||
*/
|
||||
export function findCheckboxesLine(line: string): RegExpMatchArray | null {
|
||||
return line.match(/^(\s*)([*+-]|\d+\.) \[([ x-])\] /);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает список изменений (позиции и новые значения), но не модифицирует текст напрямую.
|
||||
*/
|
||||
export function syncCheckboxes(text: string): { line: number; ch: number; value: string }[] {
|
||||
const lines = text.split("\n");
|
||||
let updates: { line: number; ch: number; value: string }[] = [];
|
||||
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const match = findCheckboxesLine(lines[i]);
|
||||
if (!match) continue;
|
||||
|
||||
const indent = match[1].length;
|
||||
const isChecked = match[3] === "x";
|
||||
let allChildrenChecked = true;
|
||||
let hasChildren = false;
|
||||
let j = i + 1;
|
||||
|
||||
while (j < lines.length) {
|
||||
const childMatch = findCheckboxesLine(lines[j]);
|
||||
if (!childMatch || childMatch[1].length <= indent) break;
|
||||
hasChildren = true;
|
||||
if (childMatch[3] !== "x") allChildrenChecked = false;
|
||||
j++;
|
||||
}
|
||||
|
||||
if (hasChildren) {
|
||||
const checkboxPos = match[1].length + match[2].length + 2;
|
||||
if (allChildrenChecked && !isChecked) {
|
||||
updates.push({ line: i, ch: checkboxPos, value: "x" });
|
||||
} else if (!allChildrenChecked && isChecked) {
|
||||
updates.push({ line: i, ch: checkboxPos, value: " " });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
18
src/main.ts
Normal file
18
src/main.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Plugin, Editor } from "obsidian";
|
||||
import { syncCheckboxes } from "./checkboxUtils";
|
||||
|
||||
export default class CheckboxSyncPlugin extends Plugin {
|
||||
async onload() {
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("editor-change", (editor) => {
|
||||
const updates = syncCheckboxes(editor.getValue());
|
||||
if (updates.length > 0) {
|
||||
editor.blur();
|
||||
updates.forEach(({ line, ch, value }) => {
|
||||
editor.replaceRange(value, { line, ch }, { line, ch: ch + 1 });
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,15 @@
|
|||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
]
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
// "rootDir": "src", // Указываем, где исходный код
|
||||
// "outDir": "dist", // Куда компилировать
|
||||
"resolveJsonModule": true // Поддержка импорта JSON файлов
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"src/**/*.ts", // Включаем только исходники из папки src
|
||||
"__tests__/**/*.ts" // Включаем тесты из папки __tests__
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue