trmnlp-test 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cfefb51546891c33222e11f7c2ba094168787d9207966a1addbc307462290fd2
4
+ data.tar.gz: 4323d3400e827f4d88913f1d624ccf7d64bd65e0f15428add123b8c3af01e782
5
+ SHA512:
6
+ metadata.gz: 2b4b67e735b844dc2510eac564e98a825217516709ef67fd68e78294ffddb40bdc99075045e1390b09dde904e3bc2311b8974ffa1d70bf230f4ab6d36b1996a1
7
+ data.tar.gz: 6b590003ac56c97e22392b85376d3fcce7bb22d172e25636a02489ac9a0304b8c3d6ed83c7c8b9f5e8a06b2598379607b872e4bdf82085827686f617a78f9a67
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ExcuseMi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # trmnlp-test
2
+
3
+ Test framework for TRMNL plugins in the trmnlp format (`src/*.liquid`, `settings.yml`, `.trmnlp.yml`), in Docker.
4
+
5
+ - **Every data path:** webhook (`deep_merge`, `stream`, 2 kB / 5 kB limits), webhook + serverless (the transform runs when data arrives and its output is stored), polling (URLs, headers and body rendered with custom fields; JSON/XML), static, serverless transforms in Python, Ruby, Node and PHP, and `trmnl_state` carried between runs.
6
+ - **Same code as trmnlp:** trmnlp's own Liquid environment, settings parsing, polling parser and serverless wrapper. The image is rebuilt on every new trmnlp release (`:latest`, `:trmnlp-X.Y.Z`) once the self-tests pass on it.
7
+ - **Every device and setting:** every model from `/api/models`, their palettes from `/api/palettes`, portrait, real mashup slots, dark mode, themes, scale, text scale, fonts, and every framework version.
8
+ - **Deterministic:** frozen clock (transform via libfaketime, Liquid `now`, browser `Date`); HTTP(S) mocks for transforms (a MITM proxy, so they work with any library) and for polling and browser requests; cached framework assets and fonts.
9
+ - **Assertions on HTML or PNG:** Playwright locators on the rendered page, plus a picture reduced to the device palette for pixel checks, QR decoding, snapshots and the device image size limit.
10
+ - **Report:** a gallery `index.html` with every device picture, transform time and memory, and problems found, linked to the Playwright HTML report.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ gem install trmnlp-test # needs Ruby and Docker; runs ghcr.io/excusemi/trmnlp-test
16
+ ```
17
+
18
+ ## Use
19
+
20
+ ```sh
21
+ cd my-plugin
22
+ trmnlp-test init # trmnlp-test.config.js + test/trmnl/plugin.spec.js
23
+ trmnlp-test # run; Playwright args pass through (-g, --update-snapshots)
24
+ trmnlp-test models | versions # device models / framework versions
25
+ trmnlp-test update # pull the image now (otherwise at most once a day)
26
+ ```
27
+
28
+ `--repo DIR` runs against another directory. `--mount DIR` mounts an extra directory.
29
+
30
+ **Parallel:** tests run in parallel workers, each with its own harness and browser (default: half the CPUs). Set the number with `workers: 4` (or `'50%'`) in the config, `TRMNLP_TEST_WORKERS=4`, or `--workers 4` / `-j 4`, in increasing priority. Separate runs can also go at the same time, for other plugins or the same one: the shared cache is safe for concurrent use. Give each run of the same repo its own `--report DIR`. The cache (assets, dependencies, mock CA) is kept in `~/.cache/trmnlp-test`.
31
+
32
+ From a clone, `bin/trmnlp-test` builds the image from source on the newest trmnlp (`TRMNLP_TEST_BUILD=1` rebuilds it; `TRMNLP_VERSION=x.y.z` picks another).
33
+
34
+ ## Release
35
+
36
+ Pushing to `main`, and a daily check for a new trmnlp release, publish the image as `:latest` and `:trmnlp-X.Y.Z`. Pushing a tag `vX.Y.Z` (matching `lib/trmnlp_test/version.rb`) runs the self-tests, publishes `:X.Y.Z` and pushes the gem through rubygems.org trusted publishing (no stored key).
37
+
38
+ ## Config: `trmnlp-test.config.js`
39
+
40
+ See [templates/trmnlp-test.config.js](templates/trmnlp-test.config.js). Transform dependencies:
41
+
42
+ ```js
43
+ serverless: { dependencies: { python: ['icalendar==6.1.0'], ruby: 'Gemfile', node: ['date-fns@4'], php: ['nesbot/carbon:^3'] } }
44
+ ```
45
+
46
+ `requests` (Python) and `httparty` (Ruby) are always installed, as on the hosted runtime. Each distinct list is installed once.
47
+
48
+ ## Tests
49
+
50
+ ```js
51
+ const { test, expect, matrix, VIEWS, DEVICES, FRAMEWORK } = require('trmnlp-test');
52
+
53
+ test('menu', async ({ trmnl }) => {
54
+ const screen = await trmnl.render({
55
+ device: 'og_plus', view: 'full', // any model, orientation: 'portrait', palette, darkMode, theme, scale, textScale, fonts, framework
56
+ fields: { data_source: 'webhook' }, // custom field values (defaults + .trmnlp.yml + these)
57
+ webhook: { merge_variables: {...} }, // or data: {...} to render given data as is
58
+ mocks: { 'https://api.example.com/*': { temp: 21 } },
59
+ transform: true, // false: skip the transform
60
+ now: '2026-10-02T08:00:00Z',
61
+ });
62
+ expect(screen).toRenderCleanly(); // Liquid, transform, page JS, assets, unmocked requests, screen size
63
+ await expect(screen.locator('[data-row]')).toHaveCount(7); // any Playwright locator assertion
64
+ await expect(screen).toHaveNoOverflow();
65
+ await expect(screen).toHaveNoOverlap('.title', 'img');
66
+ await expect(screen).toHaveQr('BCD\n002...');
67
+ await expect(screen).toMatchScreen(); // PNG snapshot (--update-snapshots)
68
+ await expect(screen).toFitDeviceImageLimit();
69
+ const png = await screen.png(); // pixel(x,y), colors(), inkRatio(rect), inkBounds(), crop()
70
+ });
71
+
72
+ test('transform only', async ({ trmnl }) => {
73
+ const run = await trmnl.transform({ mocks, fields });
74
+ expect(run).toTransformCleanly();
75
+ expect(run).toStayWithinServerlessLimits(); // 5 s, 128 MB
76
+ expect(run).toHaveRequested('https://api.example.com/x', { headers: { 'x-key': 'abc' } });
77
+ expect(run.output).toMatchObject({ ... });
78
+ });
79
+
80
+ test('a device over time', async ({ trmnl }) => {
81
+ const s = trmnl.session({ mocks });
82
+ await s.webhook({ merge_variables: {...}, merge_strategy: 'deep_merge' });
83
+ const screen = await s.render(); // stored webhook data + trmnl_state from the last run
84
+ });
85
+
86
+ for (const s of matrix({ device: DEVICES.representative, view: VIEWS, darkMode: [false, true] })) {
87
+ test(`fits · ${s.label}`, async ({ trmnl }) => { /* trmnl.render(s) */ });
88
+ }
89
+ ```
90
+
91
+ Other helpers: `trmnl.plugin('other/dir')`, `trmnl.lint()` with `toPassLint({ allow })`, `FRAMEWORK.majors()`, `FRAMEWORK.latestOf('2')`.
92
+
93
+ ## How it works
94
+
95
+ See [docs/README.md](docs/README.md) for diagrams of the components, a render, sessions and the file structure.
96
+
97
+ ## Examples and self-tests
98
+
99
+ - `examples/payment-qr`: the payment QR plugin, current Liquid version and the earlier webhook + serverless version (53 tests).
100
+ - `test/self`: fixture plugins for every strategy and language (86 tests). Run with `bin/trmnlp-test run --root test/self`.
data/exe/trmnlp-test ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Runs trmnlp-test (a Docker image) against a TRMNL plugin repository.
5
+ # trmnlp-test [--repo DIR] [--mount DIR]... [run|init|models|versions|refresh|update|shell|version] [playwright args]
6
+ require 'etc'
7
+ require 'fileutils'
8
+ require_relative '../lib/trmnlp_test/version'
9
+
10
+ # :latest follows the newest trmnlp release; TRMNLP_TEST_IMAGE pins one (e.g. ...:trmnlp-0.13.1)
11
+ image = ENV.fetch('TRMNLP_TEST_IMAGE', "#{TrmnlpTest::IMAGE}:latest")
12
+ repo = Dir.pwd
13
+ mounts = []
14
+ args = ARGV.dup
15
+ while %w[--repo --mount].include?(args.first)
16
+ flag, dir = args.shift(2)
17
+ abort "trmnlp-test: #{flag} needs a directory" unless dir
18
+ path = File.realpath(dir)
19
+ flag == '--repo' ? repo = path : mounts += ['-v', "#{path}:#{path}"]
20
+ end
21
+
22
+ case args.first
23
+ when 'version'
24
+ puts "trmnlp-test #{TrmnlpTest::VERSION} (#{image})"
25
+ exit
26
+ when 'update'
27
+ exec 'docker', 'pull', image
28
+ end
29
+
30
+ abort 'trmnlp-test needs Docker: https://docs.docker.com/get-docker/' unless system('docker info', out: File::NULL, err: File::NULL)
31
+
32
+ cache = ENV.fetch('TRMNLP_TEST_CACHE_DIR', File.join(Dir.home, '.cache', 'trmnlp-test'))
33
+ FileUtils.mkdir_p(cache)
34
+
35
+ # a missing image is pulled; a :latest image is refreshed at most once a day
36
+ present = system('docker', 'image', 'inspect', image, out: File::NULL, err: File::NULL)
37
+ stamp = File.join(cache, '.pulled-' + image.gsub(/[^A-Za-z0-9.]+/, '_'))
38
+ stale = image.end_with?(':latest') && (!File.exist?(stamp) || Time.now - File.mtime(stamp) > 86_400)
39
+ if !present || (stale && !ENV['TRMNLP_TEST_NO_PULL'])
40
+ warn "trmnlp-test: pulling #{image}"
41
+ pulled = system('docker', 'pull', '-q', image, out: File::NULL)
42
+ abort("trmnlp-test: could not pull #{image}") if !pulled && !present
43
+ FileUtils.touch(stamp) if pulled
44
+ end
45
+ entry = []
46
+ if args.first == 'shell'
47
+ args.shift
48
+ entry = ['--entrypoint', 'bash']
49
+ end
50
+ tty = $stdout.tty? ? ['-t'] : []
51
+ env = %w[CI TRMNLP_TEST_DEBUG TRMNLP_TEST_WORKERS FORCE_COLOR].flat_map { |k| ['-e', k] }
52
+ exec 'docker', 'run', '--rm', '-i', *tty, '--init', '--ipc=host', '-u', "#{Process.uid}:#{Process.gid}",
53
+ '-v', "#{repo}:#{repo}", '-w', repo, '-v', "#{cache}:/cache", *mounts, *env, *entry, image, *args
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrmnlpTest
4
+ VERSION = '0.1.2'
5
+ IMAGE = 'ghcr.io/excusemi/trmnlp-test'
6
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trmnlp-test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - ExcuseMi
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: 'Runs Playwright tests against TRMNL plugins in the trmnlp format: webhook,
13
+ polling, static and serverless transforms (Python, Ruby, Node, PHP) with mocked
14
+ HTTP and a frozen clock, on every TRMNL device model and framework version. The
15
+ launcher runs the ghcr.io/excusemi/trmnlp-test Docker image.'
16
+ executables:
17
+ - trmnlp-test
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - exe/trmnlp-test
24
+ - lib/trmnlp_test/version.rb
25
+ homepage: https://github.com/ExcuseMi/trmnlp-test
26
+ licenses:
27
+ - MIT
28
+ metadata:
29
+ source_code_uri: https://github.com/ExcuseMi/trmnlp-test
30
+ rubygems_mfa_required: 'true'
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '3.0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubygems_version: 3.6.9
46
+ specification_version: 4
47
+ summary: Test framework for TRMNL plugins (trmnlp projects, serverless, webhooks,
48
+ every device)
49
+ test_files: []