frozen_rails 0.0.4 → 0.0.6

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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -2
  3. data/LICENSE.txt +1 -1
  4. data/README.md +46 -82
  5. data/Rakefile +20 -0
  6. data/lib/frozen_rails/appendable.rb +9 -2
  7. data/lib/frozen_rails/copyable.rb +7 -2
  8. data/lib/frozen_rails/generator.rb +0 -2
  9. data/lib/frozen_rails/taggable.rb +3 -2
  10. data/lib/frozen_rails/version.rb +1 -3
  11. data/lib/frozen_rails.rb +0 -2
  12. data/lib/generators/frozen/db_generator.rb +2 -5
  13. data/lib/generators/frozen/md_generator.rb +6 -3
  14. data/lib/generators/frozen/rails_generator.rb +6 -14
  15. data/lib/generators/frozen/seo_generator.rb +5 -7
  16. data/lib/generators/frozen/ssg_generator.rb +0 -2
  17. data/lib/generators/frozen/templates/db/database.yml +15 -2
  18. data/lib/generators/frozen/templates/db/initializers/skip_controller_tests_and_helpers.rb +9 -0
  19. data/lib/generators/frozen/templates/db/lib/generators/avo/resource/resource_generator.rb +1 -1
  20. data/lib/generators/frozen/templates/db/lib/templates/active_record/model/model.rb.tt +12 -1
  21. data/lib/generators/frozen/templates/db/lib/templates/erb/scaffold/index.html.erb.tt +14 -8
  22. data/lib/generators/frozen/templates/db/lib/templates/erb/scaffold/partial.html.erb.tt +2 -2
  23. data/lib/generators/frozen/templates/db/lib/templates/erb/scaffold/show.html.erb.tt +8 -7
  24. data/lib/generators/frozen/templates/md/assets/application.css +13 -0
  25. data/lib/generators/frozen/templates/md/assets/rouge/README.md +7 -0
  26. data/lib/generators/frozen/templates/md/assets/rouge/revert.css +11 -0
  27. data/lib/generators/frozen/templates/md/views/categories/index.html.erb +5 -3
  28. data/lib/generators/frozen/templates/md/views/categories/show.html.erb +5 -9
  29. data/lib/generators/frozen/templates/md/views/pages/show.html.erb +6 -3
  30. data/lib/generators/frozen/templates/ssg/.gitlab-ci.yml +34 -0
  31. data/lib/generators/frozen/templates/ui/application.html.erb +27 -0
  32. data/lib/generators/frozen/templates/ui/application_component.rb +2 -0
  33. data/lib/generators/frozen/templates/ui/assets/application.js +4 -0
  34. data/lib/generators/frozen/templates/ui/assets/builds/.keep +0 -0
  35. data/lib/generators/frozen/templates/ui/assets/components/index.js +2 -0
  36. data/lib/generators/frozen/templates/ui/assets/fonts/.keep +0 -0
  37. data/lib/generators/frozen/templates/ui/assets/images/.keep +0 -0
  38. data/lib/generators/frozen/templates/ui/assets/images/pages/.keep +0 -0
  39. data/lib/generators/frozen/templates/ui/assets/jasmine/boot.js +6 -0
  40. data/lib/generators/frozen/templates/ui/assets/jasmine/support/fixtures.js +24 -0
  41. data/lib/generators/frozen/templates/ui/assets/jasmine/support/jasmine-dom.js +1 -0
  42. data/lib/generators/frozen/templates/ui/assets/jasmine/support/unpoly-test-config.js +7 -0
  43. data/lib/generators/frozen/templates/ui/assets/jasmine/support.js +1 -0
  44. data/lib/generators/frozen/templates/ui/assets/jasmine.js +5 -0
  45. data/lib/generators/frozen/templates/ui/assets/rouge/index.js +2 -0
  46. data/lib/generators/frozen/templates/ui/assets/static.js +5 -0
  47. data/lib/generators/frozen/templates/ui/assets/theme/README.md +11 -0
  48. data/lib/generators/frozen/templates/ui/assets/theme/base.css +27 -0
  49. data/lib/generators/frozen/templates/ui/assets/theme/code.css +29 -0
  50. data/lib/generators/frozen/templates/ui/assets/theme/forms.css +160 -0
  51. data/lib/generators/frozen/templates/ui/assets/theme/index.js +2 -0
  52. data/lib/generators/frozen/templates/ui/assets/theme/links.css +8 -0
  53. data/lib/generators/frozen/templates/ui/assets/theme/misc.css +150 -0
  54. data/lib/generators/frozen/templates/ui/assets/theme/parts.css +8 -0
  55. data/lib/generators/frozen/templates/ui/assets/theme/print.css +53 -0
  56. data/lib/generators/frozen/templates/ui/assets/theme/range.css +87 -0
  57. data/lib/generators/frozen/templates/ui/assets/theme/select-arrow.svg +4 -0
  58. data/lib/generators/frozen/templates/ui/assets/theme/typography.css +89 -0
  59. data/lib/generators/frozen/templates/ui/assets/theme/variables.css +49 -0
  60. data/lib/generators/frozen/templates/ui/assets/unpoly/index.js +7 -0
  61. data/lib/generators/frozen/templates/ui/demo_component/counter_component.css +18 -0
  62. data/lib/generators/frozen/templates/ui/demo_component/counter_component.html.erb +6 -0
  63. data/lib/generators/frozen/templates/ui/demo_component/counter_component.js +24 -0
  64. data/lib/generators/frozen/templates/ui/demo_component/counter_component.rb +6 -0
  65. data/lib/generators/frozen/templates/ui/demo_component/counter_component.yml +5 -0
  66. data/lib/generators/frozen/templates/ui/demo_component_tests/counter_component_preview.rb +54 -0
  67. data/lib/generators/frozen/templates/ui/demo_component_tests/counter_component_spec.js +25 -0
  68. data/lib/generators/frozen/templates/ui/demo_component_tests/counter_component_test.rb +40 -0
  69. data/lib/generators/frozen/templates/ui/demo_component_tests/docs/styleguide.md.erb +30 -0
  70. data/lib/generators/frozen/templates/ui/demo_component_tests/manual_test_component_spec.js +14 -0
  71. data/lib/generators/frozen/templates/ui/esbuild.config.js +79 -0
  72. data/lib/generators/frozen/templates/ui/jasmine/controllers/jasmine_controller.rb +12 -0
  73. data/lib/generators/frozen/templates/ui/jasmine/views/jasmine/index.html.erb +19 -0
  74. data/lib/generators/frozen/templates/ui/jasmine/views/layouts/jasmine.html.erb +17 -0
  75. data/lib/generators/frozen/templates/ui/lib/generators/component/component_generator.rb +72 -0
  76. data/lib/generators/frozen/templates/ui/lib/tasks/assets.rake +14 -0
  77. data/lib/generators/frozen/templates/ui/lib/templates/view_component/component/component.rb.tt +19 -0
  78. data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/bem_stylesheet.css.tt +3 -0
  79. data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/erb_template.html.erb.tt +5 -0
  80. data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/jasmine_spec.js.tt +15 -0
  81. data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/unpoly_compiler.js.tt +5 -0
  82. data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/view_spec.rb.tt +21 -0
  83. data/lib/generators/frozen/templates/ui/lib/templates/view_component/preview/component_preview.rb.tt +15 -0
  84. data/lib/generators/frozen/templates/ui/lookbook/lookbook_helper.rb +38 -0
  85. data/lib/generators/frozen/ui_generator.rb +135 -41
  86. metadata +68 -10
  87. data/lib/generators/frozen/templates/ui/cable.yml +0 -5
  88. data/lib/generators/frozen/templates/ui/hotkey_controller.js +0 -9
  89. /data/lib/generators/frozen/templates/db/initializers/{readonly_routes.rb → skip_writing_routes.rb} +0 -0
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="62.5" width="116.9">
3
+ <path d="M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z"/>
4
+ </svg>
@@ -0,0 +1,89 @@
1
+ h1 {
2
+ font-size: 2.2em;
3
+ margin-top: 0;
4
+ }
5
+
6
+ h1,
7
+ h2,
8
+ h3,
9
+ h4,
10
+ h5,
11
+ h6 {
12
+ margin-bottom: 12px;
13
+ margin-top: 24px;
14
+ }
15
+
16
+ h1,
17
+ h2,
18
+ h3,
19
+ h4,
20
+ h5,
21
+ h6,
22
+ strong {
23
+ color: var(--text-bright);
24
+ }
25
+
26
+ h1,
27
+ h2,
28
+ h3,
29
+ h4,
30
+ h5,
31
+ h6,
32
+ b,
33
+ strong,
34
+ th {
35
+ font-weight: 600;
36
+ }
37
+
38
+ q::before {
39
+ content: none;
40
+ }
41
+
42
+ q::after {
43
+ content: none;
44
+ }
45
+
46
+ blockquote,
47
+ q {
48
+ border-left: 4px solid var(--focus);
49
+ margin: 1.5em 0;
50
+ padding: 0.5em 1em;
51
+ font-style: italic;
52
+ }
53
+
54
+ blockquote > footer {
55
+ font-style: normal;
56
+ border: 0;
57
+ }
58
+
59
+ blockquote cite {
60
+ font-style: normal;
61
+ }
62
+
63
+ address {
64
+ font-style: normal;
65
+ }
66
+
67
+ a[href^='mailto\:']::before {
68
+ content: '📧 ';
69
+ }
70
+
71
+ a[href^='tel\:']::before {
72
+ content: '📞 ';
73
+ }
74
+
75
+ a[href^='sms\:']::before {
76
+ content: '💬 ';
77
+ }
78
+
79
+ mark {
80
+ background-color: var(--highlight);
81
+ border-radius: 2px;
82
+ padding: 0 2px 0 2px;
83
+ color: #000;
84
+ }
85
+
86
+ a > code,
87
+ a > strong {
88
+ color: inherit;
89
+ }
@@ -0,0 +1,49 @@
1
+ :root {
2
+ --background-body: #fff;
3
+ --background: #efefef;
4
+ --background-alt: #f7f7f7;
5
+ --selection: #9e9e9e;
6
+ --text-main: #363636;
7
+ --text-bright: #000;
8
+ --text-muted: #70777f;
9
+ --links: #0076d1;
10
+ --focus: #0096bfab;
11
+ --border: #dbdbdb;
12
+ --code: #000;
13
+ --animation-duration: 0.1s;
14
+ --button-base: #d0cfcf;
15
+ --button-hover: #9b9b9b;
16
+ --scrollbar-thumb: rgb(170, 170, 170);
17
+ --scrollbar-thumb-hover: var(--button-hover);
18
+ --form-placeholder: #949494;
19
+ --form-text: #1d1d1d;
20
+ --variable: #39a33c;
21
+ --highlight: #ff0;
22
+ --select-arrow: url("select-arrow.svg");
23
+ }
24
+
25
+ @media (prefers-color-scheme: dark) {
26
+ :root {
27
+ --background-body: #202b38;
28
+ --background: #161f27;
29
+ --background-alt: #1a242f;
30
+ --selection: #1c76c5;
31
+ --text-main: #dbdbdb;
32
+ --text-bright: #fff;
33
+ --text-muted: #a9b1ba;
34
+ --links: #41adff;
35
+ --focus: #0096bfab;
36
+ --border: #526980;
37
+ --code: #ffbe85;
38
+ --animation-duration: 0.1s;
39
+ --button-base: #0c151c;
40
+ --button-hover: #040a0f;
41
+ --scrollbar-thumb: var(--button-hover);
42
+ --scrollbar-thumb-hover: rgb(0, 0, 0);
43
+ --form-placeholder: #a9a9a9;
44
+ --form-text: #fff;
45
+ --variable: #d941e2;
46
+ --highlight: #efdb43;
47
+ --select-arrow: url("select-arrow.svg");
48
+ }
49
+ }
@@ -0,0 +1,7 @@
1
+ // Import Unpoly (side effect: sets window.up)
2
+ import 'unpoly'
3
+ import 'unpoly/unpoly.css'
4
+
5
+ up.link.config.followSelectors.push('a[href]')
6
+ up.link.config.preloadSelectors.push('a[href]')
7
+ up.form.config.submitSelectors.push(['form'])
@@ -0,0 +1,18 @@
1
+ .counter {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 0.5rem;
5
+ }
6
+
7
+ .counter button {
8
+ min-width: 2.25rem;
9
+ min-height: 2.25rem;
10
+ padding: 0.35rem 0.75rem;
11
+ }
12
+
13
+ .counter span {
14
+ min-width: 2rem;
15
+ display: inline-block;
16
+ text-align: center;
17
+ font-weight: 600;
18
+ }
@@ -0,0 +1,6 @@
1
+ <div class="counter" data-compiler="counter" up-data="<%= { initialValue: @initial_value }.to_json %>">
2
+ <% if @label %><h2><%= t('.label') %></h2><% end %>
3
+ <button type="button" data-element="counter:decrement">−</button>
4
+ <span data-element="counter:value"><%= @initial_value %></span>
5
+ <button type="button" data-element="counter:increment">+</button>
6
+ </div>
@@ -0,0 +1,24 @@
1
+ up.compiler('[data-compiler="counter"]', (element, { initialValue }) => {
2
+ const display = element.querySelector('[data-element="counter:value"]')
3
+ const decrementButton = element.querySelector('[data-element="counter:decrement"]')
4
+ const incrementButton = element.querySelector('[data-element="counter:increment"]')
5
+
6
+ let value = Number(initialValue || 0)
7
+
8
+ const updateDisplay = () => {
9
+ display.textContent = value
10
+ }
11
+
12
+ const changeValue = (delta) => {
13
+ value += delta
14
+ updateDisplay()
15
+ }
16
+
17
+ const destructors = [
18
+ up.on(decrementButton, 'click', () => changeValue(-1)),
19
+ up.on(incrementButton, 'click', () => changeValue(1))
20
+ ]
21
+
22
+ updateDisplay()
23
+ return destructors
24
+ })
@@ -0,0 +1,6 @@
1
+ class CounterComponent < ApplicationComponent
2
+ def initialize(initial_value: 0, label: false)
3
+ @initial_value = initial_value
4
+ @label = label
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ en:
2
+ label: "Bean counter"
3
+
4
+ de:
5
+ label: "Erbsenzähler"
@@ -0,0 +1,54 @@
1
+ class CounterComponentPreview < ViewComponent::Preview
2
+ # A counter with custom initial value
3
+ # ---
4
+ # You probably don't want to use this component for real,
5
+ # because it doesn't persist the counter value anywhere.
6
+ #
7
+ # @label Custom
8
+ # @param initial_value [Integer]
9
+ def custom(initial_value: 0)
10
+ render CounterComponent.new(initial_value: initial_value)
11
+ end
12
+
13
+ # @!group Initial Values
14
+
15
+ # @label Default
16
+ def zero
17
+ render CounterComponent.new
18
+ end
19
+
20
+ # @label Manually set
21
+ def forty_two
22
+ render CounterComponent.new(initial_value: 42)
23
+ end
24
+
25
+ # @!endgroup
26
+
27
+ # @hidden
28
+ def negative_one
29
+ render CounterComponent.new(initial_value: -1)
30
+ end
31
+
32
+ # @!group I18n
33
+
34
+ # @label English
35
+ def english
36
+ I18n.with_locale(:en) do
37
+ render CounterComponent.new(label: true)
38
+ end
39
+ end
40
+
41
+ # Caution
42
+ # ---
43
+ # Getting too sophisticated within a preview isn't supported:
44
+ # - [https://github.com/lookbook-hq/lookbook/issues/793](https://github.com/lookbook-hq/lookbook/issues/793)
45
+ #
46
+ # @label German [Lookbook BUG, see Notes tab]
47
+ def german
48
+ I18n.with_locale(:de) do
49
+ render CounterComponent.new(label: true)
50
+ end
51
+ end
52
+
53
+ # @!endgroup
54
+ end
@@ -0,0 +1,25 @@
1
+ describe('CounterComponent', () => {
2
+ let component, decrement, increment, value
3
+
4
+ beforeEach(() => {
5
+ const container = loadPreviewScenario('counter', 'zero')
6
+ component = container.querySelector('.counter')
7
+ decrement = component.querySelector('[data-element="counter:decrement"]')
8
+ increment = component.querySelector('[data-element="counter:increment"]')
9
+ value = component.querySelector('[data-element="counter:value"]')
10
+ })
11
+
12
+ it('shows the initial value', () => {
13
+ expect(value).toContainText('0')
14
+ })
15
+
16
+ it('increments the counter when the increment button is clicked', () => {
17
+ increment.click()
18
+ expect(value).toContainText('1')
19
+ })
20
+
21
+ it('decrements the counter when the decrement button is clicked', () => {
22
+ decrement.click()
23
+ expect(value).toContainText('-1')
24
+ })
25
+ })
@@ -0,0 +1,40 @@
1
+ require "test_helper"
2
+ require "view_component/test_helpers"
3
+
4
+ class CounterComponentTest < ActionView::TestCase
5
+ include ViewComponent::TestHelpers
6
+ include LookbookHelper
7
+
8
+ def test_renders_counter_structure
9
+ render_inline(CounterComponent.new(initial_value: 5))
10
+
11
+ assert_selector ".counter"
12
+ assert_selector "button[data-element='counter:decrement']", text: "−"
13
+ assert_selector "span[data-element='counter:value']", text: "5"
14
+ assert_selector "button[data-element='counter:increment']", text: "+"
15
+
16
+ data = JSON.parse(page.find(".counter")["up-data"])
17
+ assert_equal 5, data["initialValue"]
18
+ end
19
+
20
+ def test_defaults_to_zero
21
+ render_inline(CounterComponent.new)
22
+
23
+ assert_selector "span[data-element='counter:value']", text: "0"
24
+ end
25
+
26
+ # https://viewcomponent.org/guide/previews.html#previews-as-test-cases
27
+ def test_render_view_component_preview
28
+ render_preview(:zero)
29
+
30
+ assert_selector "span[data-element='counter:value']", text: "0"
31
+ end
32
+
33
+ # See `app/helpers/lookbook_helper.rb`.
34
+ def test_render_lookbook_preview
35
+ render_preview_scenario("counter", "zero")
36
+ @page = Capybara.string(rendered)
37
+
38
+ assert_selector "span[data-element='counter:value']", text: "0"
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ # Component Styleguide
2
+
3
+ - Use the provided `component` generator to scaffold new components. It also supports namespaces:
4
+ - `bin/rails g component Users::AvatarComponent`.
5
+ - Prefer a plural for your namespaces to avoid clashing with your models.
6
+ - Put your components into `app/components` and their tests into `test/components`.
7
+ - Keep filenames consistent.
8
+ - Delete files which you don't need for your component.
9
+
10
+ ## ViewComponent
11
+
12
+ - Use `ApplicationComponent` as the base class.
13
+ - Prefer using slots over configuration via attributes.
14
+ - Consider using a Lookbook preview as the starting point for your view specs.
15
+ - Prefer private methods within components over helpers. Use concerns to share behavior.
16
+
17
+ ## Unpoly Compilers
18
+
19
+ - Use `data-compiler` on the root element and `data-element` to connect your JS logic to the DOM.
20
+ - Pass data into components via `up-data`.
21
+ - Return a list of destructors from your compiler if necessary: [Unpoly Docs: destructor](https://unpoly.com/enhancing-elements#destructor)
22
+ - Consider using a Lookbook preview as the starting point for your Jasmine specs.
23
+
24
+ ## CSS
25
+
26
+ - Use BEM-style CSS.
27
+ - Keep consistent with the naming conventions from the provided generator:
28
+ - We use an abbreviated style that differs from regular BEM.
29
+ - No underscores at all.
30
+ - Modifiers are just `-modifier` without repeating the block name.
@@ -0,0 +1,14 @@
1
+ describe('ManualTestComponent', () => {
2
+ let heading, paragraph
3
+
4
+ beforeEach(() => {
5
+ const container = loadPreviewScenario('manual_test', 'with_multiple_top_level_tags')
6
+ heading = container.querySelector('h1')
7
+ paragraph = container.querySelector('p')
8
+ })
9
+
10
+ it('loads both tags correctly', () => {
11
+ expect(heading).toContainText('Heading')
12
+ expect(paragraph).toContainText('Paragraph')
13
+ })
14
+ })
@@ -0,0 +1,79 @@
1
+ import { build, context, formatMessages } from 'esbuild'
2
+ import esbuildPluginImportGlob from 'esbuild-plugin-import-glob'
3
+ import esbuildPluginTextReplace from 'esbuild-plugin-text-replace'
4
+ import manifestPlugin from 'esbuild-manifest-plugin'
5
+ const { default: importGlob } = esbuildPluginImportGlob
6
+
7
+ const path = await import('path')
8
+ const fs = await import('fs')
9
+
10
+ const railsEnv = process.env.RAILS_ENV || 'development'
11
+ const outdir = path.join(process.cwd(), 'public/assets')
12
+ const errorFilePath = path.join(outdir, `esbuild_error_${railsEnv}.txt`)
13
+
14
+ if (!fs.existsSync(outdir)) { fs.mkdirSync(outdir) }
15
+
16
+ async function handleErrors(errors) {
17
+ const formattedErrors = await formatMessages(errors, { kind: 'error' })
18
+ const output = formattedErrors.join('\n')
19
+
20
+ if (output) {
21
+ fs.writeFileSync(errorFilePath, output)
22
+ } else if (fs.existsSync(errorFilePath)) {
23
+ fs.truncate(errorFilePath, 0, () => {})
24
+ }
25
+ }
26
+
27
+ const config = {
28
+ entryPoints: [
29
+ 'application.js',
30
+ 'jasmine.js',
31
+ 'static.js'
32
+ ],
33
+ bundle: true,
34
+ sourcemap: true,
35
+ format: 'esm',
36
+ outdir,
37
+ absWorkingDir: path.join(process.cwd(), 'app/assets'),
38
+ metafile: true,
39
+ publicPath: '/assets',
40
+ entryNames: '[dir]/[name]-[hash]',
41
+ assetNames: '[dir]/[name]-[hash]',
42
+ loader: {
43
+ '.css': 'css',
44
+ '.ico': 'copy',
45
+ '.jpg': 'copy',
46
+ '.png': 'copy',
47
+ '.svg': 'copy',
48
+ '.webp': 'copy',
49
+ '.json': 'copy',
50
+ '.ttf': 'file',
51
+ '.woff': 'file',
52
+ '.woff2': 'file',
53
+ },
54
+ plugins: [
55
+ esbuildPluginTextReplace({
56
+ include: /jasmine-core\/lib\/jasmine-core\/jasmine\.js/,
57
+ pattern: [
58
+ ['let jasmineRequire;', 'let jasmineRequire; const global = window;'],
59
+ ]
60
+ }),
61
+ importGlob(),
62
+ manifestPlugin(),
63
+ {
64
+ name: 'handleErrors',
65
+ setup: (build) => {
66
+ build.onEnd(async ({ errors }) => {
67
+ await handleErrors(errors)
68
+ })
69
+ }
70
+ }
71
+ ],
72
+ }
73
+
74
+ if (process.argv.includes('--watch')) {
75
+ const ctx = await context(config)
76
+ ctx.watch()
77
+ } else {
78
+ await build(config)
79
+ }
@@ -0,0 +1,12 @@
1
+ class JasmineController < ApplicationController
2
+ helper :all
3
+
4
+ content_security_policy do |policy|
5
+ policy.script_src :self, :unsafe_eval
6
+ end
7
+
8
+ etag { SecureRandom.hex(16) }
9
+
10
+ def index
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ <div class="test-cases">
2
+ <% Lookbook.previews.each do |preview| %>
3
+ <% preview.send(:scenario_entities).each do |scenario| %>
4
+ <template data-component="<%= preview.lookup_path %>" data-scenario="<%= scenario.name %>">
5
+ <%= render_preview_scenario(preview, scenario) %>
6
+ </template>
7
+ <% end %>
8
+ <% end %>
9
+
10
+ <!--
11
+ This is a manual test scenario with multiple top-level tags to verify that the test framework can handle it.
12
+ Neither the ViewComponent::Preview nor Lookbook::Preview classes support multiple top-level tags, but if you
13
+ need such a test case, you can define it directly in the Jasmine layout as shown here.
14
+ -->
15
+ <template data-component="manual_test" data-scenario="with_multiple_top_level_tags">
16
+ <h1>Heading</h1>
17
+ <p>Paragraph</p>
18
+ </template>
19
+ </div>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0">
5
+ <meta http-equiv="Pragma" content="no-cache">
6
+ <meta http-equiv="Expires" content="0">
7
+ <%= stylesheet_link_tag 'jasmine' %>
8
+
9
+ <!-- Referencing npm assets from HTML is currently broken. -->
10
+ <!-- <link href="<%#= asset_path('jasmine_favicon.png') %>" rel="icon" type="image/png"> -->
11
+ </head>
12
+
13
+ <body>
14
+ <%= yield %>
15
+ <%= javascript_include_tag 'jasmine' %>
16
+ </body>
17
+ </html>
@@ -0,0 +1,72 @@
1
+ class ComponentGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path("../../templates/view_component/frozen_extensions", __dir__)
3
+
4
+ argument :attrs, type: :array, default: [], banner: "attribute"
5
+
6
+ def generate_component_with_preview_and_locale
7
+ in_root do
8
+ system("bin/rails generate view_component:component #{name} #{attrs.join(" ")} --preview --preview_path=test/components --locale --skip-test-framework --skip-template-engine")
9
+ end
10
+ end
11
+
12
+ def add_erb_template
13
+ template "erb_template.html.erb.tt", File.join("app/components", class_path, "#{file_name}_component.html.erb")
14
+ end
15
+
16
+ def add_css_file
17
+ template "bem_stylesheet.css.tt", File.join("app/components", class_path, "#{file_name}_component.css")
18
+ end
19
+
20
+ def add_unpoly_compiler
21
+ template "unpoly_compiler.js.tt", File.join("app/components", class_path, "#{file_name}_component.js")
22
+ end
23
+
24
+ def add_view_spec
25
+ template "view_spec.rb.tt", File.join("test/components", class_path, "#{file_name}_component_test.rb")
26
+ end
27
+
28
+ def add_jasmine_spec
29
+ template "jasmine_spec.js.tt", File.join("test/components", class_path, "#{file_name}_component_spec.js")
30
+ end
31
+
32
+ def fix_module_nesting_via_rubocop_because_it_was_impossible_to_get_module_namespacing_to_work_as_expected
33
+ in_root do
34
+ system("bash", "-c", 'bin/rubocop --only Style/ClassAndModuleChildren,Layout/IndentationWidth,Layout/CommentIndentation --autocorrect-all --force-exclusion --config <(echo "Style/ClassAndModuleChildren: { EnforcedStyle: nested }")')
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def js_attributes
41
+ attrs.map { |attr| attr.camelize(:lower) }
42
+ end
43
+
44
+ def css_attributes
45
+ attrs.map(&:dasherize)
46
+ end
47
+
48
+ def bem_selectors
49
+ base = file_path.gsub("/", "---").dasherize
50
+ [
51
+ base,
52
+ *css_attributes.map { |attr| "#{base}--#{attr}" }
53
+ ]
54
+ end
55
+
56
+ def up_data
57
+ data = js_attributes.zip(attrs).map { |js_attr, attr| "#{js_attr}: @#{attr}" }.join(", ")
58
+ "{ #{data} }"
59
+ end
60
+
61
+ def up_data_params
62
+ data = js_attributes.join(", ")
63
+ "{ #{data} }"
64
+ end
65
+
66
+ # https://github.com/ViewComponent/view_component/blob/main/lib/generators/view_component/preview/preview_generator.rb#L32
67
+ def render_signature
68
+ return if attributes.blank?
69
+
70
+ attributes.map { |attr| %(#{attr.name}: "#{attr.name}") }.join(", ")
71
+ end
72
+ end
@@ -0,0 +1,14 @@
1
+ if Rake::Task.task_defined?("assets:precompile")
2
+ Rake::Task["assets:precompile"].clear
3
+ end
4
+
5
+ namespace :assets do
6
+ desc "Build frontend assets with yarn"
7
+ task :precompile do
8
+ system("yarn build", exception: true)
9
+ end
10
+ end
11
+
12
+ if Rake::Task.task_defined?("test:prepare")
13
+ Rake::Task["test:prepare"].enhance([ "assets:precompile" ])
14
+ end
@@ -0,0 +1,19 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %><%= options[:skip_suffix] ? "" : "Component" %> < <%= parent_class %>
3
+ <%- if inline_template? -%>
4
+ <%= template_engine %>_template <<~<%= template_engine.upcase %>
5
+ <h1>Hello, World!</h1>
6
+ <%= template_engine.upcase %>
7
+ <%- end -%>
8
+ <%- if initialize_signature? -%>
9
+ def initialize(<%= initialize_signature %>)
10
+ <%= initialize_body %>
11
+ end
12
+ <%- end -%>
13
+ <%- if initialize_call_method_for_inline? -%>
14
+ def call
15
+ content_tag :h1, "Hello world!"<%= ", data: { controller: \"#{stimulus_controller}\" }" if options["stimulus"] %>
16
+ end
17
+ <%- end -%>
18
+ end
19
+ <% end -%>
@@ -0,0 +1,3 @@
1
+ <% bem_selectors.each do |selector| -%>
2
+ <%= selector %> {}
3
+ <% end -%>
@@ -0,0 +1,5 @@
1
+ <div class="<%= file_path.gsub('/', '---').dasherize %>" data-compiler="<%= file_path.gsub('/', '-') %>" up-data="<%%= <%= "{ #{attrs.map { |attr| "#{attr.camelize(:lower)}: @#{attr}" }.join(", ")} }" %>.to_json %>">
2
+ <% css_attributes.zip(js_attributes, attrs).each do |css_attr, js_attr, ruby_attr| -%>
3
+ <div class="<%= file_path.gsub('/', '---').dasherize %>--<%= css_attr %>" data-element="<%= file_path.gsub('/', '-') %>:<%= js_attr %>"><%%= @<%= ruby_attr %> %></div>
4
+ <% end -%>
5
+ </div>
@@ -0,0 +1,15 @@
1
+ describe('[data-compiler="<%= file_path.gsub('/', '-') %>"]', () => {
2
+ let <%= ["component", *js_attributes].join(', ') %>
3
+
4
+ beforeEach(() => {
5
+ const container = loadPreviewScenario('<%= file_path.gsub('/', '-') %>', 'default')
6
+ component = container.querySelector('.<%= file_path.gsub('/', '---').dasherize %>')
7
+ <% js_attributes.each do |attribute| -%>
8
+ <%= attribute %> = component.querySelector('[data-element="<%= file_path.gsub('/', '-') %>:<%= attribute %>"]')
9
+ <% end -%>
10
+ })
11
+
12
+ it('works', () => {
13
+ // TODO: implement spec
14
+ })
15
+ })
@@ -0,0 +1,5 @@
1
+ up.compiler('[data-compiler="<%= file_path.gsub('/', '-') %>"]', (element, <%= up_data_params %>) => {
2
+ <% js_attributes.each do |attribute| -%>
3
+ const <%= attribute %>Element = element.querySelector('[data-element="<%= file_path.gsub('/', '-') %>:<%= attribute %>"]')
4
+ <% end -%>
5
+ })