feat(playground): add ETAF example package

Provide a runnable ETAF-only example and an optional UI catalog entry point with lifecycle-safe open/close commands, tests, and bilingual README documentation.
This commit is contained in:
Kinneyzhang 2026-08-05 06:59:22 +08:00
commit bce23f6ef0
6 changed files with 188 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.elc
tests/*.elc

28
Makefile Normal file
View File

@ -0,0 +1,28 @@
EMACS ?= emacs
LOAD_PATH = -L . -L ../etaf -L ../etaf-ui -L ../emacs-box
.PHONY: all compile test check checkdoc load clean
all: check
compile:
rm -f *.elc tests/*.elc
$(EMACS) -Q --batch $(LOAD_PATH) --eval '(setq load-prefer-newer t)' \
--eval '(load-file "etaf-playground.el")' \
--eval '(byte-compile-file "etaf-playground.el")'
test: compile
$(EMACS) -Q --batch $(LOAD_PATH) --eval '(setq load-prefer-newer t)' \
-l tests/etaf-playground-tests.el -f ert-run-tests-batch-and-exit
load: compile
$(EMACS) -Q --batch $(LOAD_PATH) --eval '(require (quote etaf-playground))' \
--eval '(princ "etaf-playground load OK\n")'
checkdoc:
$(EMACS) -Q --batch --eval '(progn (require (quote checkdoc)) (dolist (file (directory-files "." t)) (when (string-suffix-p ".el" file) (checkdoc-file file))))'
check: checkdoc compile test
clean:
rm -f *.elc tests/*.elc

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# etaf-playground
`etaf-playground` is the runnable example application for ETAF. The core example depends only on `etaf`; the optional UI example loads `etaf-ui` when requested. It never imports Ebox internals and it is independent from `ebox-playground`.
```elisp
(require 'etaf-playground)
(etaf-playground-open)
```
Run `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs` from this directory.

10
README.zh-CN.md Normal file
View File

@ -0,0 +1,10 @@
# etaf-playground
`etaf-playground` 是 ETAF 的可运行示例应用。核心示例只依赖 `etaf`;需要时才通过可选入口加载 `etaf-ui`。它不调用 Ebox 私有实现,也不依赖 `ebox-playground`
```elisp
(require 'etaf-playground)
(etaf-playground-open)
```
在该目录运行 `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs`

97
etaf-playground.el Normal file
View File

@ -0,0 +1,97 @@
;;; etaf-playground.el --- ETAF public examples -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;; Author: ETAF contributors
;; Version: 0.1.0
;; Package-Requires: ((emacs "29.1") (etaf "0.1.0"))
;; URL: https://github.com/ginqi7/etaf-playground
;;; Commentary:
;; This package is a runnable example application, not a second framework
;; layer. The core example depends only on ETAF. The optional catalog demo
;; loads `etaf-ui' only when its public command is called.
;;; Code:
(require 'cl-lib)
(require 'etaf)
(defconst etaf-playground-buffer-name "*ETAF Playground*"
"Default buffer name used by `etaf-playground-open'.")
;;;###autoload
(etaf-define-component etaf-playground-counter (&key title)
"Render a retained counter titled TITLE."
:setup
(let ((count (etaf-ref 0 :name 'playground-count)))
(lambda ()
(etaf-view
(column
:class "etaf-playground-counter"
(text :face 'bold (expr :value title))
(text (expr :value (format "Count: %d" (etaf-value count))))
(row
(text :ref 'decrement :role 'button :tab-index 0
:on-press (lambda () (cl-decf (etaf-value count)))
" - ")
(text :ref 'increment :role 'button :tab-index 0
:on-press (lambda () (cl-incf (etaf-value count)))
" + ")))))))
;;;###autoload
(defun etaf-playground-view ()
"Return the core ETAF playground View without side effects."
(etaf-view
(column
:class "etaf-playground-root"
(text :face 'bold "ETAF Playground")
(text :color "#687386"
"One grammar: Hosts, Components, props, children, and expr.")
(etaf-playground-counter :title "Reactive counter")
(text :color "#687386"
"Press + or - through the public event dispatch path."))))
;;;###autoload
(defun etaf-playground-ui-view ()
"Return a View demonstrating the optional official `etaf-ui' catalog."
(require 'etaf-ui)
(etaf-view
(column
(etaf-panel :title "Official Components"
(etaf-button :label "A public Component"
:on-press (lambda () (message "ETAF UI button pressed")))
(etaf-checkbox :label "Controlled checkbox"
:on-change (lambda (value)
(message "Checkbox value: %s" value))))
(etaf-playground-counter :title "Core counter"))))
;;;###autoload
(defun etaf-playground-open (&optional buffer-name)
"Mount the core playground in BUFFER-NAME and return its buffer."
(interactive)
(etaf-mount (or buffer-name etaf-playground-buffer-name)
(etaf-playground-view)))
;;;###autoload
(defun etaf-playground-open-ui (&optional buffer-name)
"Mount the playground with the catalog in BUFFER-NAME."
(interactive)
(etaf-mount (or buffer-name etaf-playground-buffer-name)
(etaf-playground-ui-view)))
;;;###autoload
(defun etaf-playground-close (&optional buffer-name)
"Unmount and kill the playground BUFFER-NAME, returning its buffer."
(interactive)
(let* ((name (or buffer-name etaf-playground-buffer-name))
(buffer (get-buffer name)))
(when-let ((runtime (and buffer (etaf-runtime-for-buffer buffer))))
(etaf-unmount runtime))
(when buffer
(kill-buffer buffer))
buffer))
(provide 'etaf-playground)
;;; etaf-playground.el ends here

View File

@ -0,0 +1,41 @@
;;; etaf-playground-tests.el --- ETAF example tests -*- lexical-binding: t; -*-
(require 'ert)
(require 'etaf-playground)
(ert-deftest etaf-playground-mounts-and-dispatches-core-example ()
"Mount the public playground and update its retained counter."
(let ((buffer-name " *etaf-playground-test*"))
(unwind-protect
(progn
(etaf-mount buffer-name (etaf-playground-view))
(with-current-buffer buffer-name
(should (string-match-p "ETAF Playground" (buffer-string)))
(should (string-match-p "Count: 0" (buffer-string))))
(etaf-dispatch-event (etaf-runtime-for-buffer buffer-name)
'increment 'press)
(with-current-buffer buffer-name
(should (string-match-p "Count: 1" (buffer-string)))))
(when-let ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(ert-deftest etaf-playground-ui-entry-keeps-catalog-optional ()
"Load the catalog only through the optional UI entry point."
(require 'etaf-ui)
(let ((buffer-name " *etaf-playground-ui-test*"))
(unwind-protect
(progn
(etaf-mount buffer-name (etaf-playground-ui-view))
(with-current-buffer buffer-name
(should (string-match-p "Official Components" (buffer-string)))
(should (string-match-p "Controlled checkbox" (buffer-string)))))
(when-let ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(provide 'etaf-playground-tests)
;;; etaf-playground-tests.el ends here