commit bce23f6ef0200d662c896dffe3d132b65f8741b1 Author: Kinneyzhang Date: Wed Aug 5 06:59:22 2026 +0800 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. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3daa17c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.elc +tests/*.elc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9a502ff --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..11dcd6d --- /dev/null +++ b/README.md @@ -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. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..57bede2 --- /dev/null +++ b/README.zh-CN.md @@ -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`。 diff --git a/etaf-playground.el b/etaf-playground.el new file mode 100644 index 0000000..e335df6 --- /dev/null +++ b/etaf-playground.el @@ -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 diff --git a/tests/etaf-playground-tests.el b/tests/etaf-playground-tests.el new file mode 100644 index 0000000..7cdc3b0 --- /dev/null +++ b/tests/etaf-playground-tests.el @@ -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