trmnl_preview 0.23.1 → 0.25.0
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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +2 -0
- data/db/data/framework_classes.yml +1043 -0
- data/lib/trmnlp/lint/checks/author_contact_method.rb +28 -0
- data/lib/trmnlp/lint/checks/coordinates_use_lat_lon.rb +40 -0
- data/lib/trmnlp/lint/checks/framework_classes_exist.rb +42 -0
- data/lib/trmnlp/lint/checks/limited_inline_styles.rb +2 -1
- data/lib/trmnlp/lint/checks/no_external_qr_codes.rb +23 -0
- data/lib/trmnlp/lint/checks/no_nested_layouts.rb +25 -0
- data/lib/trmnlp/lint/checks/title_bar_outside_layout.rb +25 -0
- data/lib/trmnlp/lint/checks/yes_no_selects_are_boolean.rb +35 -0
- data/lib/trmnlp/lint/diagnostic.rb +4 -1
- data/lib/trmnlp/lint/source.rb +19 -0
- data/lib/trmnlp/lint.rb +15 -1
- data/lib/trmnlp/testing/publishable_recipe.rb +2 -1
- data/lib/trmnlp/testing/report.html.erb +7 -0
- data/lib/trmnlp/testing/report.rb +29 -3
- data/lib/trmnlp/version.rb +1 -1
- metadata +9 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../check'
|
|
4
|
+
|
|
5
|
+
module TRMNLP
|
|
6
|
+
module Lint
|
|
7
|
+
module Checks
|
|
8
|
+
# Recipe review asks for a contact that is not Discord, because not every
|
|
9
|
+
# user has access to Discord.
|
|
10
|
+
class AuthorContactMethod < Check
|
|
11
|
+
MESSAGE = 'Add an email address, web link or GitHub link to the author_bio custom field, ' \
|
|
12
|
+
'so users can reach you with questions or issues. A Discord link alone is not enough.'
|
|
13
|
+
LEARN_MORE = 'https://help.trmnl.com/en/articles/10513740-custom-plugin-form-builder#h_02dd8f84a9'
|
|
14
|
+
CONTACT_KEYS = %w[email_address github_url learn_more_url description].freeze
|
|
15
|
+
CONTACT_PATTERN = %r{[^\s@]+@[^\s@]+\.\w+|https?://\S+}
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def pass?
|
|
20
|
+
author_bio = source.custom_field_definitions.find { it['field_type'] == 'author_bio' }
|
|
21
|
+
return false unless author_bio
|
|
22
|
+
|
|
23
|
+
author_bio.values_at(*CONTACT_KEYS).join(' ').scan(CONTACT_PATTERN).any? { !it.match?(/discord/i) }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../check'
|
|
4
|
+
|
|
5
|
+
module TRMNLP
|
|
6
|
+
module Lint
|
|
7
|
+
module Checks
|
|
8
|
+
# Reports separate latitude and longitude fields in settings.yml. One
|
|
9
|
+
# lat_lon field asks for both, and lets the user pick a place on a map.
|
|
10
|
+
class CoordinatesUseLatLon < Check
|
|
11
|
+
LATITUDE_WORDS = %w[lat latitude].freeze
|
|
12
|
+
LONGITUDE_WORDS = %w[lon lng long longitude].freeze
|
|
13
|
+
LEARN_MORE = 'https://help.trmnl.com/en/articles/10513740-custom-plugin-form-builder#h_07a70ff721'
|
|
14
|
+
|
|
15
|
+
def issues
|
|
16
|
+
# A keyname with both words (lat_lng) is one field, so it never pairs with itself.
|
|
17
|
+
latitude = keynames_with(LATITUDE_WORDS) - keynames_with(LONGITUDE_WORDS)
|
|
18
|
+
longitude = keynames_with(LONGITUDE_WORDS) - keynames_with(LATITUDE_WORDS)
|
|
19
|
+
return [] if latitude.empty? || longitude.empty?
|
|
20
|
+
|
|
21
|
+
fields = (latitude + longitude).map { "'#{it}'" }.join(' and ')
|
|
22
|
+
[{ message: "Form fields #{fields} ask for one location. Use one field with field_type 'lat_lon'.",
|
|
23
|
+
learn_more: LEARN_MORE }]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def keynames_with(words) = keynames.select { |keyname| words.intersect?(words_in(keyname)) }
|
|
29
|
+
|
|
30
|
+
def keynames
|
|
31
|
+
source.custom_field_definitions.reject { |field| field['field_type'] == 'lat_lon' }
|
|
32
|
+
.map { |field| field['keyname'].to_s }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# home_lat, homeLat and home-lat all give %w[home lat].
|
|
36
|
+
def words_in(keyname) = keyname.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.split(/[^a-z\d]+/)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
require_relative '../check'
|
|
6
|
+
|
|
7
|
+
module TRMNLP
|
|
8
|
+
module Lint
|
|
9
|
+
module Checks
|
|
10
|
+
# Reports classes in the Framework's own families that the plugin's release does
|
|
11
|
+
# not define, such as `value--medium`. They match no rule, so they do nothing.
|
|
12
|
+
class FrameworkClassesExist < Check
|
|
13
|
+
FAMILY_PATTERN = /\A(?:value|label|title|description|text|content)--/
|
|
14
|
+
DATA_PATH = File.expand_path('../../../../db/data/framework_classes.yml', __dir__)
|
|
15
|
+
|
|
16
|
+
# Release number => the family classes its plugins.css defines, from that release until the next one listed.
|
|
17
|
+
CLASSES_BY_RELEASE = YAML.load_file(DATA_PATH).transform_keys { Gem::Version.new(it) }.sort.freeze
|
|
18
|
+
|
|
19
|
+
def issues
|
|
20
|
+
(used_classes - release_classes).map do |name|
|
|
21
|
+
{ message: "'#{name}' is not a class in Framework v#{source.framework_version}, so it has no effect. " \
|
|
22
|
+
'Check the name in the Framework docs.' }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def used_classes
|
|
29
|
+
source.html_fragments.values.flat_map { it.css('[class]').flat_map { it['class'].split } }
|
|
30
|
+
.map { it.split(':').last.to_s }
|
|
31
|
+
.select { it.match?(FAMILY_PATTERN) && !it.include?('{{') }
|
|
32
|
+
.uniq
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def release_classes
|
|
36
|
+
version = Gem::Version.new(source.framework_version.number)
|
|
37
|
+
CLASSES_BY_RELEASE.select { |release, _| release <= version }.last.last
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../check'
|
|
4
|
+
require_relative '../source'
|
|
4
5
|
require 'crass'
|
|
5
6
|
require 'nokogiri'
|
|
6
7
|
|
|
@@ -25,7 +26,7 @@ module TRMNLP
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def markup_without_liquid_comments
|
|
28
|
-
source.all_markup.gsub(
|
|
29
|
+
source.all_markup.gsub(Source::LIQUID_COMMENT, '')
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../check'
|
|
4
|
+
|
|
5
|
+
module TRMNLP
|
|
6
|
+
module Lint
|
|
7
|
+
module Checks
|
|
8
|
+
class NoExternalQrCodes < Check
|
|
9
|
+
MESSAGE = 'Instead of an external service, please use our built-in Liquid filter for QR codes "qr_code".'
|
|
10
|
+
LEARN_MORE = 'https://help.trmnl.com/en/articles/10347358-custom-plugin-filters#h_1c2c240f9e'
|
|
11
|
+
QR_GENERATORS = %r{
|
|
12
|
+
(?:qrserver\.com|goqr\.me|qrcode\.tec-it\.com|qrcode-monkey\.com
|
|
13
|
+
|quickchart\.io/qr\b|chart\.googleapis\.com/chart\?[^"')]*\bcht=qr\b)
|
|
14
|
+
}xi
|
|
15
|
+
PATTERN = %r{(?:\bsrc\s*=\s*|\burl\(\s*)["']?(?:https?:)?//(?:[\w-]+\.)*#{QR_GENERATORS}}i
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def pass? = !source.all_markup.match?(PATTERN)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../check'
|
|
4
|
+
|
|
5
|
+
module TRMNLP
|
|
6
|
+
module Lint
|
|
7
|
+
module Checks
|
|
8
|
+
class NoNestedLayouts < Check
|
|
9
|
+
MESSAGE = "A 'layout' is inside another 'layout', which breaks the render. " \
|
|
10
|
+
"Use one 'layout' per view, with 'columns' or 'flex' inside it."
|
|
11
|
+
LEARN_MORE = 'https://trmnl.com/framework/docs/layout'
|
|
12
|
+
|
|
13
|
+
def misplaced_elements
|
|
14
|
+
@misplaced_elements ||= source.html_fragments.flat_map do |path, fragment|
|
|
15
|
+
fragment.css('.layout .layout').map { [path, it] }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def pass? = misplaced_elements.empty?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../check'
|
|
4
|
+
|
|
5
|
+
module TRMNLP
|
|
6
|
+
module Lint
|
|
7
|
+
module Checks
|
|
8
|
+
class TitleBarOutsideLayout < Check
|
|
9
|
+
MESSAGE = "A 'title_bar' is inside a 'layout', so it does not render in its place. " \
|
|
10
|
+
"Move it out of the 'layout', after it."
|
|
11
|
+
LEARN_MORE = 'https://trmnl.com/framework/docs/title_bar'
|
|
12
|
+
|
|
13
|
+
def misplaced_elements
|
|
14
|
+
@misplaced_elements ||= source.html_fragments.flat_map do |path, fragment|
|
|
15
|
+
fragment.css('.layout .title_bar').map { [path, it] }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def pass? = misplaced_elements.empty?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../check'
|
|
4
|
+
|
|
5
|
+
module TRMNLP
|
|
6
|
+
module Lint
|
|
7
|
+
module Checks
|
|
8
|
+
# Reports a settings.yml select whose only options are yes and no — one
|
|
9
|
+
# finding per field. A boolean field shows the same choice as a switch.
|
|
10
|
+
class YesNoSelectsAreBoolean < Check
|
|
11
|
+
YES_NO_VALUE_SETS = [%w[no yes], %w[false true]].freeze
|
|
12
|
+
LEARN_MORE = 'https://help.trmnl.com/en/articles/10513740-custom-plugin-form-builder'
|
|
13
|
+
|
|
14
|
+
def issues
|
|
15
|
+
source.custom_field_definitions.select { |field| yes_no_select?(field) }.map do |field|
|
|
16
|
+
{ message: "Form field '#{field['keyname']}' is a select with only Yes and No options. " \
|
|
17
|
+
"Use field_type 'boolean' to show a switch.", learn_more: LEARN_MORE }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def yes_no_select?(field)
|
|
24
|
+
options = field['options']
|
|
25
|
+
field['field_type'] == 'select' && options.is_a?(Array) && YES_NO_VALUE_SETS.include?(option_values(options))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Same value TRMNL submits: a Label: value pair's value, otherwise the option itself.
|
|
29
|
+
def option_values(options)
|
|
30
|
+
options.map { |option| (option.is_a?(Hash) ? option.values.first : option).to_s.downcase }.uniq.sort
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -13,6 +13,7 @@ module TRMNLP
|
|
|
13
13
|
'no_async_functions' => /async function/i,
|
|
14
14
|
'no_opacity' => Checks::NoOpacity::PATTERN,
|
|
15
15
|
'no_size_classes' => Checks::NoSizeClasses::PATTERN,
|
|
16
|
+
'no_external_qr_codes' => Checks::NoExternalQrCodes::PATTERN,
|
|
16
17
|
'waits_for_dom_load' => Regexp.new(Regexp.union(Checks::WaitsForDomLoad::FORBIDDEN).source, Regexp::IGNORECASE),
|
|
17
18
|
'limited_inline_styles' => /\sstyle\s*=/i,
|
|
18
19
|
'highcharts_animations_disabled' => /highcharts/i,
|
|
@@ -40,11 +41,13 @@ module TRMNLP
|
|
|
40
41
|
return source.locations(MARKUP_PATTERNS[rule_id]) if MARKUP_PATTERNS.key?(rule_id)
|
|
41
42
|
|
|
42
43
|
case rule_id
|
|
43
|
-
when 'arbitrary_values_in_range' then class_locations
|
|
44
|
+
when 'arbitrary_values_in_range', 'framework_classes_exist' then class_locations
|
|
44
45
|
when 'layouts_have_content' then empty_view_locations
|
|
45
46
|
when 'form_fields_valid' then form_field_locations
|
|
46
47
|
when 'custom_fields_used' then project_field_locations
|
|
47
48
|
when 'no_custom_filters', 'no_unknown_filters' then filter_locations
|
|
49
|
+
when 'title_bar_outside_layout', 'no_nested_layouts'
|
|
50
|
+
check.misplaced_elements.map { |path, element| source.element_location(path, element) }
|
|
48
51
|
when 'image_links_reachable' then source.locations(Regexp.union(check.unreachable_urls))
|
|
49
52
|
else []
|
|
50
53
|
end
|
data/lib/trmnlp/lint/source.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'nokogiri'
|
|
3
4
|
require 'trmnl/liquid'
|
|
4
5
|
require 'yaml'
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ module TRMNLP
|
|
|
12
13
|
# which Config::Plugin's semantic readers render away.
|
|
13
14
|
class Source
|
|
14
15
|
VIEWS = %w[full half_horizontal half_vertical quadrant].freeze
|
|
16
|
+
LIQUID_COMMENT = /\{%-?\s*comment\s*-?%\}.*?\{%-?\s*endcomment\s*-?%\}/m
|
|
15
17
|
|
|
16
18
|
def initialize(config:, paths:)
|
|
17
19
|
@config = config
|
|
@@ -60,6 +62,23 @@ module TRMNLP
|
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
|
|
65
|
+
# Parsed without render data: Liquid output becomes `{{}}` and tags become spaces,
|
|
66
|
+
# so the classes of every branch count and a quote inside Liquid cannot end an attribute.
|
|
67
|
+
# Each replacement keeps the newlines it replaced, so element lines stay true.
|
|
68
|
+
def html_fragments
|
|
69
|
+
@html_fragments ||= markup_files.transform_values do |markup|
|
|
70
|
+
html = markup.gsub(LIQUID_COMMENT) { "\n" * it.count("\n") }
|
|
71
|
+
.gsub(/\{\{.*?\}\}/m) { "{{}}#{"\n" * it.count("\n")}" }
|
|
72
|
+
.gsub(/\{%.*?%\}/m) { " #{"\n" * it.count("\n")}" }
|
|
73
|
+
Nokogiri::HTML.fragment(html)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def element_location(path, element)
|
|
78
|
+
snippet = markup_files.fetch(path).lines[element.line - 1].to_s.chomp
|
|
79
|
+
{ path:, line: element.line, column: (snippet.index(/<#{element.name}\b/i) || 0) + 1, snippet: snippet[0, 240] }
|
|
80
|
+
end
|
|
81
|
+
|
|
63
82
|
def locations(pattern, files: markup_files)
|
|
64
83
|
files.flat_map do |path, contents|
|
|
65
84
|
contents.to_enum(:scan, pattern).map do
|
data/lib/trmnlp/lint.rb
CHANGED
|
@@ -18,8 +18,15 @@ require_relative 'lint/checks/highcharts_elements_unique'
|
|
|
18
18
|
require_relative 'lint/checks/image_links_reachable'
|
|
19
19
|
require_relative 'lint/checks/custom_fields_used'
|
|
20
20
|
require_relative 'lint/checks/form_fields_valid'
|
|
21
|
+
require_relative 'lint/checks/yes_no_selects_are_boolean'
|
|
22
|
+
require_relative 'lint/checks/coordinates_use_lat_lon'
|
|
21
23
|
require_relative 'lint/checks/no_custom_filters'
|
|
22
24
|
require_relative 'lint/checks/no_unknown_filters'
|
|
25
|
+
require_relative 'lint/checks/author_contact_method'
|
|
26
|
+
require_relative 'lint/checks/title_bar_outside_layout'
|
|
27
|
+
require_relative 'lint/checks/no_nested_layouts'
|
|
28
|
+
require_relative 'lint/checks/framework_classes_exist'
|
|
29
|
+
require_relative 'lint/checks/no_external_qr_codes'
|
|
23
30
|
|
|
24
31
|
module TRMNLP
|
|
25
32
|
# Markup best-practice checks behind `trmnlp lint`.
|
|
@@ -42,8 +49,15 @@ module TRMNLP
|
|
|
42
49
|
Checks::ImageLinksReachable,
|
|
43
50
|
Checks::CustomFieldsUsed,
|
|
44
51
|
Checks::FormFieldsValid,
|
|
52
|
+
Checks::YesNoSelectsAreBoolean,
|
|
53
|
+
Checks::CoordinatesUseLatLon,
|
|
45
54
|
Checks::NoCustomFilters,
|
|
46
|
-
Checks::NoUnknownFilters
|
|
55
|
+
Checks::NoUnknownFilters,
|
|
56
|
+
Checks::AuthorContactMethod,
|
|
57
|
+
Checks::TitleBarOutsideLayout,
|
|
58
|
+
Checks::NoNestedLayouts,
|
|
59
|
+
Checks::FrameworkClassesExist,
|
|
60
|
+
Checks::NoExternalQrCodes
|
|
47
61
|
].freeze
|
|
48
62
|
|
|
49
63
|
def self.rule_id(check_type) = check_type.name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
@@ -43,7 +43,8 @@ RSpec.shared_examples 'a publishable recipe' do |screens: TRMNLP::Testing::PUBLI
|
|
|
43
43
|
|
|
44
44
|
TRMNLP::Testing::PUBLISHABLE_RECIPE_VIEWS.product(screens).each do |view, screen|
|
|
45
45
|
device = screen[:device].is_a?(Hash) ? screen[:device][:model] : screen[:device]
|
|
46
|
-
|
|
46
|
+
dark_mode = 'dark mode' if screen[:dark_mode]
|
|
47
|
+
screen_name = [device, screen[:orientation], screen[:palette], dark_mode].compact.join(' ')
|
|
47
48
|
|
|
48
49
|
it "draws the #{view} view on #{screen_name} without page errors" do
|
|
49
50
|
expect(trmnl.render(view:, **screen, **recipe_inputs)).to draw_cleanly
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
pre { background: #f6f8fa; padding: 8px; overflow-x: auto; white-space: pre-wrap; }
|
|
21
21
|
.screens { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 8px; }
|
|
22
22
|
figure { margin: 0; max-width: 100%; }
|
|
23
|
+
.og-vs-x { display: block; max-width: 100%; height: auto; border: 1px solid var(--line); }
|
|
23
24
|
.frame { position: relative; border: 1px solid var(--line); max-width: min(100%, 640px); }
|
|
24
25
|
.frame img, .frame svg { display: block; width: 100%; height: auto; }
|
|
25
26
|
.frame svg { position: absolute; inset: 0; display: none; }
|
|
@@ -47,6 +48,12 @@
|
|
|
47
48
|
<label for="failures">failures only</label>
|
|
48
49
|
</header>
|
|
49
50
|
<main>
|
|
51
|
+
<% if og_vs_x %>
|
|
52
|
+
<section>
|
|
53
|
+
<h2>Full view on TRMNL OG and TRMNL X, at the same height</h2>
|
|
54
|
+
<figure><img src="<%= og_vs_x %>" alt="TRMNL OG and TRMNL X side by side" class="og-vs-x"></figure>
|
|
55
|
+
</section>
|
|
56
|
+
<% end %>
|
|
50
57
|
<% examples.each do |example| %>
|
|
51
58
|
<section class="<%= 'failed-example' if example[:status] == 'failed' %>">
|
|
52
59
|
<h2 class="<%= example[:status] %>"><%= h(example[:description]) %></h2>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'erb'
|
|
4
4
|
require 'fileutils'
|
|
5
5
|
require 'json'
|
|
6
|
+
require 'mini_magick'
|
|
6
7
|
require 'rspec/core'
|
|
7
8
|
|
|
8
9
|
module TRMNLP
|
|
@@ -12,6 +13,7 @@ module TRMNLP
|
|
|
12
13
|
# DIR/report.json. Under GitHub Actions the counts and failures go to the step summary too.
|
|
13
14
|
class Report
|
|
14
15
|
TEMPLATE = File.expand_path('report.html.erb', __dir__)
|
|
16
|
+
OG_VS_X = 'og-vs-x.png'
|
|
15
17
|
OUTLINES_SCRIPT = <<~JS
|
|
16
18
|
(() => [...document.querySelectorAll('.view *')].slice(0, 3000).map((el) => {
|
|
17
19
|
const box = el.getBoundingClientRect();
|
|
@@ -28,12 +30,17 @@ module TRMNLP
|
|
|
28
30
|
@dir = "#{dir}#{ENV.fetch('TEST_ENV_NUMBER', '')}" # parallel_tests: report, report2, report3...
|
|
29
31
|
@examples = {}
|
|
30
32
|
@images = 0
|
|
33
|
+
@full_landscape_images = {}
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
def record_screen(screen)
|
|
34
|
-
entry
|
|
37
|
+
return unless entry
|
|
38
|
+
|
|
39
|
+
image = copy_image(screen.png_path)
|
|
40
|
+
remember_full_landscape(screen, image)
|
|
41
|
+
entry[:screens].push(
|
|
35
42
|
label: "#{screen.device.name} · #{screen.view}", width: screen.device.width, height: screen.device.height,
|
|
36
|
-
image
|
|
43
|
+
image:, outlines: screen.evaluate(OUTLINES_SCRIPT), problems: screen.problems
|
|
37
44
|
)
|
|
38
45
|
end
|
|
39
46
|
|
|
@@ -50,7 +57,8 @@ module TRMNLP
|
|
|
50
57
|
|
|
51
58
|
def close(_notification)
|
|
52
59
|
FileUtils.mkdir_p(@dir)
|
|
53
|
-
|
|
60
|
+
write_og_vs_x
|
|
61
|
+
File.write(File.join(@dir, 'report.json'), JSON.pretty_generate(og_vs_x:, examples:))
|
|
54
62
|
File.write(File.join(@dir, 'index.html'), ERB.new(File.read(TEMPLATE)).result(binding))
|
|
55
63
|
write_step_summary if ENV['GITHUB_STEP_SUMMARY']
|
|
56
64
|
end
|
|
@@ -75,6 +83,24 @@ module TRMNLP
|
|
|
75
83
|
name
|
|
76
84
|
end
|
|
77
85
|
|
|
86
|
+
def remember_full_landscape(screen, image)
|
|
87
|
+
return unless screen.view == 'full' && screen.device.width > screen.device.height
|
|
88
|
+
|
|
89
|
+
@full_landscape_images[screen.device.name] ||= image
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def og_vs_x = (OG_VS_X if @full_landscape_images.values_at('og_plus', 'v2').all?)
|
|
93
|
+
|
|
94
|
+
# Both at the OG's height, so content that fills the OG but is small on the X shows at a glance.
|
|
95
|
+
def write_og_vs_x
|
|
96
|
+
return unless og_vs_x
|
|
97
|
+
|
|
98
|
+
MiniMagick.convert do |convert|
|
|
99
|
+
@full_landscape_images.values_at('og_plus', 'v2').each { convert << File.join(@dir, it) }
|
|
100
|
+
convert << '-resize' << 'x480' << '+append' << '+repage' << File.join(@dir, OG_VS_X)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
78
104
|
def count(status) = examples.count { it[:status] == status }
|
|
79
105
|
|
|
80
106
|
def write_step_summary
|
data/lib/trmnlp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trmnl_preview
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.25.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rockwell Schrock
|
|
@@ -360,6 +360,7 @@ files:
|
|
|
360
360
|
- bin/trmnlp
|
|
361
361
|
- db/data/and_x_more.yml
|
|
362
362
|
- db/data/form_fields.yml
|
|
363
|
+
- db/data/framework_classes.yml
|
|
363
364
|
- db/data/framework_versions.yml
|
|
364
365
|
- db/data/locales/cs.yml
|
|
365
366
|
- db/data/locales/da.yml
|
|
@@ -421,9 +422,12 @@ files:
|
|
|
421
422
|
- lib/trmnlp/lint.rb
|
|
422
423
|
- lib/trmnlp/lint/check.rb
|
|
423
424
|
- lib/trmnlp/lint/checks/arbitrary_values_in_range.rb
|
|
425
|
+
- lib/trmnlp/lint/checks/author_contact_method.rb
|
|
426
|
+
- lib/trmnlp/lint/checks/coordinates_use_lat_lon.rb
|
|
424
427
|
- lib/trmnlp/lint/checks/custom_fields_used.rb
|
|
425
428
|
- lib/trmnlp/lint/checks/description_length.rb
|
|
426
429
|
- lib/trmnlp/lint/checks/form_fields_valid.rb
|
|
430
|
+
- lib/trmnlp/lint/checks/framework_classes_exist.rb
|
|
427
431
|
- lib/trmnlp/lint/checks/highcharts_animations_disabled.rb
|
|
428
432
|
- lib/trmnlp/lint/checks/highcharts_elements_unique.rb
|
|
429
433
|
- lib/trmnlp/lint/checks/image_links_reachable.rb
|
|
@@ -431,13 +435,17 @@ files:
|
|
|
431
435
|
- lib/trmnlp/lint/checks/limited_inline_styles.rb
|
|
432
436
|
- lib/trmnlp/lint/checks/no_async_functions.rb
|
|
433
437
|
- lib/trmnlp/lint/checks/no_custom_filters.rb
|
|
438
|
+
- lib/trmnlp/lint/checks/no_external_qr_codes.rb
|
|
439
|
+
- lib/trmnlp/lint/checks/no_nested_layouts.rb
|
|
434
440
|
- lib/trmnlp/lint/checks/no_opacity.rb
|
|
435
441
|
- lib/trmnlp/lint/checks/no_size_classes.rb
|
|
436
442
|
- lib/trmnlp/lint/checks/no_unknown_filters.rb
|
|
437
443
|
- lib/trmnlp/lint/checks/recipe_overview_length.rb
|
|
444
|
+
- lib/trmnlp/lint/checks/title_bar_outside_layout.rb
|
|
438
445
|
- lib/trmnlp/lint/checks/title_casing.rb
|
|
439
446
|
- lib/trmnlp/lint/checks/title_length.rb
|
|
440
447
|
- lib/trmnlp/lint/checks/waits_for_dom_load.rb
|
|
448
|
+
- lib/trmnlp/lint/checks/yes_no_selects_are_boolean.rb
|
|
441
449
|
- lib/trmnlp/lint/diagnostic.rb
|
|
442
450
|
- lib/trmnlp/lint/source.rb
|
|
443
451
|
- lib/trmnlp/merged_plugin_locals.rb
|