Skip to content
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
27 changes: 19 additions & 8 deletions .github/workflows/upload.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ permissions:
contents: read

concurrency:
group: upload-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
group: cos-upload-${{ github.repository }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'production' }}
cancel-in-progress: false

jobs:
test:
Expand All @@ -36,17 +36,27 @@ jobs:
tools: calcit,caps
caps-version: "0.1.1"

# Current Reel/Markdown/UI manifests still request preceding compatible
# alpha releases, so strict Caps resolution rejects the highest SemVer set.
# The direct js-ffi dependency is newer than the version requested by
# Respo; strict resolution is not claimed until upstream manifests align.
- name: Install and validate
run: |
set -euo pipefail
test "$(calcit --version)" = "0.27.0"
test -f calcit.cirru
test -f deps.cirru
test ! -e compact.cirru
test ! -e package.cirru
caps --ci
yarn install --immutable
caps verify --toolchain
calcit calcit.cirru edit format
git diff --exit-code -- calcit.cirru
calcit calcit.cirru fix --workflow strict --verify --format edn
calcit calcit.cirru --check-only
calcit calcit.cirru analyze check-public \
--ns fuzzy-filter.comp.container --ns fuzzy-filter.comp.visual \
--ns fuzzy-filter.config --ns fuzzy-filter.core --ns fuzzy-filter.main \
--ns fuzzy-filter.schema --ns fuzzy-filter.updater --summary-only
calcit calcit.cirru test --require-match --summary-only --format json
calcit calcit.cirru analyze check-types --summary-only --format json
calcit calcit.cirru analyze dynamic-methods --summary-only --format json
Expand All @@ -57,19 +67,20 @@ jobs:
id: asset-path
env:
CDN_ORIGIN: https://cos-sh.tiye.me
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
asset_prefix="${GITHUB_REPOSITORY}/pr/"
asset_prefix="${GITHUB_REPOSITORY}/pr/${PR_NUMBER}/"
else
asset_prefix="${GITHUB_REPOSITORY}/"
fi
export VITE_BASE_URL="${CDN_ORIGIN}/${asset_prefix}"
echo "VITE_BASE_URL=${VITE_BASE_URL}" >> "$GITHUB_ENV"
echo "prefix=${asset_prefix}" >> "$GITHUB_OUTPUT"
echo "base-url=${VITE_BASE_URL}" >> "$GITHUB_OUTPUT"
calcit calcit.cirru js
node --test scripts/fuzzy-filter.test.mjs
yarn vite build --base="$VITE_BASE_URL"
yarn build
node --test scripts/*.test.mjs

- name: Upload static assets to COS
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

.compact-inc.cirru
.calcit-error.cirru
compact.cirru
package.cirru

js-out/
node_modules/
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,28 @@ Workflow https://github.com/calcit-lang/respo-calcit-workflow

### 开发验证

项目使用 Calcit 0.24.3 与 `calcit-lang/js-ffi` 的类型化浏览器适配器处理挂载节点、本地存储和定时器。运行以下命令验证 Calcit 测试及生成的 JavaScript:
项目使用 Calcit 0.27.0 与 `calcit-lang/js-ffi` 的类型化浏览器适配器处理挂载节点、本地存储和定时器。仅使用 `calcit.cirru`、`deps.cirru`,不要恢复或提交旧的 `compact.cirru`、`package.cirru`;CI 也会拒绝被 Git 忽略但仍存在的旧文件。运行以下命令验证 Calcit 测试及生成的 JavaScript:

```bash
caps --strict --ci
caps --ci
calcit calcit.cirru --check-only
calcit calcit.cirru test --require-match
calcit calcit.cirru js
node --test scripts/fuzzy-filter.test.mjs
```

当前直接 js-ffi 版本比 Respo 的传递请求更新,Caps 会报告版本冲突;尚不宣称 `caps --strict --ci` 通过。浏览器入口显式声明 `mode: js` / `target: browser`,公共定义检查覆盖全部 7 个业务命名空间。输入回调接收两个参数,读取 `RespoEvent` 字段并派发单参数 Enum。

### Frontend deployment

前端生产构建通过 `VITE_BASE_URL` 配置 COS 路径;本地未设置时仍使用相对路径:

```bash
VITE_BASE_URL=https://cos-sh.tiye.me/mvc-works/fuzzy-filter/pr/30/ yarn build
```

上传校验使用 COS Action 内置 verify 配置,不添加额外 CDN 校验脚本。原共享字体、图标与服务器部署路径不变。

### License

MIT
18 changes: 12 additions & 6 deletions calcit.cirru
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
:about "|Machine-generated snapshot. Do not edit directly — changes will be overwritten. Use `calcit query` to inspect and `calcit edit`/`calcit tree` to modify. Run `calcit docs agents --contract` before mutations; use `--full` for first orientation or changed contract digest. Manual edits must follow format and schema conventions, then run `calcit edit format`."
:package |fuzzy-filter
:entries $ {} $ :default
{} (:description |) (:init-fn 'fuzzy-filter.main/main!) (:mode :native) (:reload-fn 'fuzzy-filter.main/reload!)
{} (:description |) (:init-fn 'fuzzy-filter.main/main!) (:mode :js) (:reload-fn 'fuzzy-filter.main/reload!) (:target :browser)
:feature-policy $ {}
:modules $ [] |respo.calcit/ |respo-ui.calcit/ |respo-markdown.calcit/ |reel.calcit/ |js-ffi/
:modules $ [] |respo.calcit/ |respo-ui.calcit/ |reel.calcit/ |js-ffi/
:type-slots $ {}
:files $ {}
'fuzzy-filter.comp.container $ %{} 'FileEntry
Expand All @@ -23,15 +23,21 @@
input $ {} (:style ui/input)
:value $ &map:get store :content
:placeholder |text
:on-input $ fn (e d! m!)
d! :content $ &map:get e :value
:on-input $ fn (e d!)
hint-fn $ {}
:args $ [] (quote respo.schema/RespoEvent) (quote Dynamic)
:return $ quote Dynamic
d! $ :: :content $ :value e
=< nil 8
div ({})
input $ {} (:style ui/input)
:value $ &map:get store :query
:placeholder |query
:on-input $ fn (e d! m!)
d! :query $ &map:get e :value
:on-input $ fn (e d!)
hint-fn $ {}
:args $ [] (quote respo.schema/RespoEvent) (quote Dynamic)
:return $ quote Dynamic
d! $ :: :query $ :value e
let
result $ parse-by-letter (&map:get store :content) (&map:get store :query)
word-result $ parse-by-word (&map:get store :content) (&map:get store :query)
Expand Down
5 changes: 2 additions & 3 deletions deps.cirru
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@

{} (:calcit-version |0.27.0)
:version |0.0.15-alpha.1
:dependencies $ {} (|Respo/reel.calcit |0.6.33-alpha.1)
|Respo/respo-markdown.calcit |0.4.46-alpha.1
|Respo/respo-ui.calcit |0.7.32-alpha.2
:dependencies $ {} (|Respo/reel.calcit |0.6.33-alpha.2)
|Respo/respo-ui.calcit |0.7.32-alpha.3
Comment thread
tiye marked this conversation as resolved.
|Respo/respo.calcit |0.16.114-alpha.5
|calcit-lang/js-ffi |0.2.1-alpha.10
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"packageManager": "yarn@4.18.0",
"description": "Some tiny text filter function",
"main": "index.js",
"scripts": {
"compile": "calcit calcit.cirru js",
"build": "yarn compile && yarn vite build",
"dev": "yarn vite"
},
"repository": {
"type": "git",
"url": "git@github.com:mvc-works/fuzzy-filter.git"
Expand Down
19 changes: 19 additions & 0 deletions scripts/cdn-build.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { test } from "node:test";

const dist = new URL("../dist/", import.meta.url);

test("built HTML loads its generated assets from the selected CDN path", () => {
const base = process.env.VITE_BASE_URL;
assert.ok(base?.startsWith("https://"), "build with VITE_BASE_URL before testing");
const html = readFileSync(new URL("index.html", dist), "utf8");
const urls = [...html.matchAll(/(?:src|href)="([^"]+)"/g)]
.map((match) => match[1])
.filter((url) => url.includes("assets/"));
assert.ok(urls.some((url) => url.endsWith(".js")), "built HTML must reference generated JavaScript");
for (const url of urls) {
assert.ok(url.startsWith(`${base}assets/`), `${url} must use ${base}`);
assert.ok(existsSync(new URL(url.slice(base.length), dist)), `${url} must exist in dist`);
}
});
56 changes: 56 additions & 0 deletions scripts/fuzzy-filter.test.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import assert from "node:assert/strict";
import test from "node:test";
import * as c from "../js-out/calcit.core.mjs";
import { comp_container } from "../js-out/fuzzy-filter.comp.container.mjs";
import { store } from "../js-out/fuzzy-filter.schema.mjs";
import { updater } from "../js-out/fuzzy-filter.updater.mjs";
import { RespoEvent } from "../js-out/respo.schema.mjs";
import {
component_$q_, component_tree, element_$q_, element_children,
element_name, element_event, element_attrs,
} from "../js-out/respo.util.detect.mjs";

import { format_cirru_edn, option_$o_unwrap_or } from "../js-out/calcit.core.mjs";
import { parse_by_letter } from "../js-out/fuzzy-filter.core.mjs";
Expand All @@ -12,6 +21,53 @@ import {
} from "../js-out/js-ffi.browser.mjs";

const formatResult = (text, query) => format_cirru_edn(parse_by_letter(text, query));
const tags = c.init_tags(['store', 'input', 'value', 'type', 'content', 'query']);
const read = (value, key) => c.option_$o_unwrap(c.get(value, key));
const render = (value) => comp_container(c._$n__$M_(tags.store, value));
const inputs = (component) => {
const found = [];
const walk = (node) => {
if (component_$q_(node)) return walk(c.option_$o_unwrap(component_tree(node)));
if (!element_$q_(node)) return;
if (element_name(node) === tags.input) found.push(node);
const children = element_children(node);
for (let index = 0; index < c.count(children); index++) {
const entry = c.option_$o_unwrap(c.nth(children, index));
walk(c.option_$o_unwrap(c.nth(entry, 1)));
}
};
walk(component);
return found;
};

for (const [index, key, value] of [[0, tags.content, 'abc'], [1, tags.query, 'ac']]) {
test(`actual ${key.value} input accepts two arguments and dispatches a single Enum`, () => {
const fields = RespoEvent.fields.flatMap(field => [field,
field === tags.value ? value : field === tags.type ? tags.input : null]);
const event = c._$n__PCT__$M_(RespoEvent, ...fields);
const rendered = inputs(render(store));
assert.equal(rendered.length, 2);
const handler = read(element_event(rendered[index]), tags.input);
Comment thread
Copilot marked this conversation as resolved.
assert.equal(handler.length, 2);
let updated = store;
handler(event, (...args) => {
assert.equal(args.length, 1);
assert.ok(c.enum_$q_(args[0]));
updated = updater(store, args[0], 'test-op', 0);
});
assert.equal(read(updated, key), value);
const rerendered = inputs(render(updated));
const attrs = element_attrs(rerendered[index]);
let renderedValue;
for (let i = 0; i < c.count(attrs); i++) {
const pair = c.option_$o_unwrap(c.nth(attrs, i));
const attrName = c.option_$o_unwrap(c.nth(pair, 0));
if (attrName === tags.value || attrName === 'value') renderedValue = c.option_$o_unwrap(c.nth(pair, 1));
}
assert.equal(renderedValue, value);
assert.equal(read(store, key), index === 0 ? 'this and that to search' : 'that search');
});
}

test("keeps unmatched suffix when the query finishes first", () => {
const result = formatResult("abc", "a");
Expand Down
3 changes: 3 additions & 0 deletions vite.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineConfig } from 'vite';

export default defineConfig({ base: process.env.VITE_BASE_URL || './' });