├── .github ├── FUNDING.yml ├── dependabot.yml ├── release.yml └── workflows │ ├── ci.yml │ ├── github-actions-lint.yml │ ├── rubocop.yml │ └── yaml-lint.yml ├── .gitignore ├── .rubocop.yml ├── .yamllint.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── arbre.gemspec ├── bin ├── bundle └── console ├── codecov.yml ├── docs ├── Gemfile ├── _config.yml ├── _includes │ ├── footer.html │ ├── google-analytics.html │ ├── head.html │ ├── toc.html │ └── top-menu.html ├── _layouts │ └── default.html ├── index.md └── stylesheets │ └── main.css ├── gemfiles ├── rails_70 │ ├── Gemfile │ └── Gemfile.lock ├── rails_71 │ ├── Gemfile │ └── Gemfile.lock └── rails_72 │ ├── Gemfile │ └── Gemfile.lock ├── lib ├── arbre.rb └── arbre │ ├── component.rb │ ├── context.rb │ ├── element.rb │ ├── element │ ├── builder_methods.rb │ └── proxy.rb │ ├── element_collection.rb │ ├── html │ ├── attributes.rb │ ├── class_list.rb │ ├── document.rb │ ├── html5_elements.rb │ ├── tag.rb │ └── text_node.rb │ ├── rails │ ├── forms.rb │ ├── rendering.rb │ └── template_handler.rb │ ├── railtie.rb │ └── version.rb └── spec ├── arbre ├── integration │ └── html_spec.rb └── unit │ ├── component_spec.rb │ ├── context_spec.rb │ ├── element_finder_methods_spec.rb │ ├── element_spec.rb │ └── html │ ├── class_list_spec.rb │ ├── document_spec.rb │ ├── tag_attributes_spec.rb │ ├── tag_spec.rb │ └── text_node_spec.rb ├── changelog_spec.rb ├── gemspec_spec.rb ├── rails ├── integration │ ├── forms_spec.rb │ └── rendering_spec.rb ├── rails_spec_helper.rb ├── stub_app │ ├── config │ │ ├── database.yml │ │ └── routes.rb │ ├── db │ │ └── schema.rb │ ├── log │ │ └── .gitignore │ └── public │ │ └── favicon.ico ├── support │ └── mock_person.rb └── templates │ ├── arbre │ ├── _partial.arb │ ├── _partial_with_assignment.arb │ ├── empty.arb │ ├── page_with_arb_partial_and_assignment.arb │ ├── page_with_assignment.arb │ ├── page_with_erb_partial.arb │ ├── page_with_partial.arb │ ├── page_with_render_with_block.arb │ └── simple_page.arb │ └── erb │ └── _partial.erb ├── spec_helper.rb └── support └── bundle.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tidelift: rubygems/arbre 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | groups: 8 | github_actions: 9 | patterns: 10 | - "*" 11 | - package-ecosystem: bundler 12 | directory: / 13 | schedule: 14 | interval: monthly 15 | versioning-strategy: lockfile-only 16 | groups: 17 | rails_default: 18 | patterns: 19 | - "*" 20 | - package-ecosystem: bundler 21 | directory: /gemfiles/rails_61 22 | schedule: 23 | interval: monthly 24 | versioning-strategy: lockfile-only 25 | groups: 26 | rails_61: 27 | patterns: 28 | - "*" 29 | - package-ecosystem: bundler 30 | directory: /gemfiles/rails_70 31 | schedule: 32 | interval: monthly 33 | versioning-strategy: lockfile-only 34 | groups: 35 | rails_70: 36 | patterns: 37 | - "*" 38 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - dependabot 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 15 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | ruby: 17 | - "3.1" 18 | - "3.2" 19 | - "3.3" 20 | - "3.4" 21 | rails: 22 | - rails_70 23 | - rails_71 24 | - rails_72 25 | - rails_80 26 | exclude: 27 | - ruby: "3.1" 28 | rails: rails_80 29 | - ruby: "3.4" 30 | rails: rails_70 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Configure bundler (default) 34 | run: | 35 | echo "BUNDLE_GEMFILE=Gemfile" >> "$GITHUB_ENV" 36 | if: matrix.rails == 'rails_80' 37 | - name: Configure bundler (alternative) 38 | run: | 39 | echo "BUNDLE_GEMFILE=gemfiles/${{ matrix.rails }}/Gemfile" >> "$GITHUB_ENV" 40 | if: matrix.rails != 'rails_80' 41 | - uses: ruby/setup-ruby@v1 42 | with: 43 | ruby-version: ${{ matrix.ruby }} 44 | bundler-cache: true 45 | - name: Run tests 46 | env: 47 | COVERAGE: true 48 | run: | 49 | bundle exec rspec 50 | mv coverage/coverage.xml coverage/coverage-ruby-${{ matrix.ruby }}-${{ matrix.rails }}.xml 51 | - uses: actions/upload-artifact@v4 52 | with: 53 | name: coverage-ruby-${{ matrix.ruby }}-${{ matrix.rails }} 54 | path: coverage 55 | if-no-files-found: error 56 | 57 | upload_coverage: 58 | name: Upload Coverage 59 | runs-on: ubuntu-latest 60 | needs: 61 | - test 62 | steps: 63 | - uses: actions/checkout@v4 64 | - uses: actions/download-artifact@v4 65 | with: 66 | pattern: coverage-ruby-* 67 | path: coverage 68 | merge-multiple: true 69 | - uses: codecov/codecov-action@v5 70 | with: 71 | token: ${{ secrets.CODECOV_TOKEN }} 72 | directory: coverage 73 | fail_ci_if_error: true 74 | -------------------------------------------------------------------------------- /.github/workflows/github-actions-lint.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Lint 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | github_actions_lint: 8 | name: Run actionlint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: tj-actions/changed-files@v46 13 | id: changed-files 14 | with: 15 | files: | 16 | .github/workflows/*.yaml 17 | .github/workflows/*.yml 18 | - uses: reviewdog/action-actionlint@v1 19 | if: steps.changed-files.outputs.any_changed == 'true' 20 | with: 21 | fail_level: any 22 | filter_mode: nofilter # added (default), diff_context, file, nofilter 23 | github_token: ${{ secrets.GITHUB_TOKEN }} 24 | reporter: github-pr-check 25 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: Rubocop 2 | 3 | on: 4 | pull_request: 5 | 6 | env: 7 | RUBY_VERSION: ${{ vars.RUBOCOP_RUBY_VERSION || '3.4' }} 8 | 9 | jobs: 10 | rubocop: 11 | name: Run rubocop 12 | runs-on: ubuntu-latest 13 | env: 14 | BUNDLE_ONLY: ${{ vars.RUBOCOP_BUNDLE_ONLY || 'rubocop' }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: tj-actions/changed-files@v46 18 | id: changed-files 19 | with: 20 | files: | 21 | .github/workflows/rubocop.yml 22 | .rubocop.yml 23 | **.rb 24 | **.arb 25 | bin/* 26 | docs/Gemfile 27 | gemfiles/**/Gemfile 28 | Gemfile* 29 | Rakefile 30 | *.gemspec 31 | - uses: ruby/setup-ruby@v1 32 | if: steps.changed-files.outputs.any_changed == 'true' 33 | with: 34 | ruby-version: ${{ env.RUBY_VERSION }} 35 | bundler-cache: true 36 | - uses: reviewdog/action-rubocop@v2 37 | if: steps.changed-files.outputs.any_changed == 'true' 38 | with: 39 | fail_level: any 40 | filter_mode: nofilter # added (default), diff_context, file, nofilter 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | skip_install: true 43 | use_bundler: true 44 | -------------------------------------------------------------------------------- /.github/workflows/yaml-lint.yml: -------------------------------------------------------------------------------- 1 | name: YAML Lint 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | yaml_lint: 8 | name: Run yamllint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: tj-actions/changed-files@v46 13 | id: changed-files 14 | with: 15 | files: | 16 | **.yaml 17 | **.yml 18 | - uses: reviewdog/action-yamllint@v1 19 | if: steps.changed-files.outputs.any_changed == 'true' 20 | with: 21 | fail_level: any 22 | filter_mode: nofilter # added (default), diff_context, file, nofilter 23 | github_token: ${{ secrets.GITHUB_TOKEN }} 24 | reporter: github-pr-check 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | pkg/* 4 | benchmarks 5 | .rvmrc 6 | .ruby-version 7 | .ruby-gemset 8 | tags 9 | .DS_Store 10 | docs/_site 11 | coverage/ 12 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | plugins: 4 | - rubocop-capybara 5 | - rubocop-packaging 6 | - rubocop-performance 7 | - rubocop-rspec 8 | 9 | AllCops: 10 | DisabledByDefault: true 11 | 12 | DisplayCopNames: true 13 | DisplayStyleGuide: true 14 | 15 | TargetRubyVersion: 3.1 16 | 17 | Capybara: 18 | Enabled: true 19 | 20 | Capybara/ClickLinkOrButtonStyle: 21 | Enabled: true 22 | 23 | Capybara/CurrentPathExpectation: 24 | Enabled: true 25 | 26 | Capybara/FindAllFirst: 27 | Enabled: true 28 | 29 | Capybara/MatchStyle: 30 | Enabled: true 31 | 32 | Capybara/NegationMatcher: 33 | Enabled: true 34 | 35 | Capybara/NegationMatcherAfterVisit: 36 | Enabled: true 37 | 38 | Capybara/RedundantWithinFind: 39 | Enabled: true 40 | 41 | Capybara/RSpec/HaveSelector: 42 | Enabled: true 43 | 44 | Capybara/RSpec/PredicateMatcher: 45 | Enabled: true 46 | 47 | Capybara/SpecificActions: 48 | Enabled: true 49 | 50 | Capybara/SpecificFinders: 51 | Enabled: true 52 | 53 | Capybara/SpecificMatcher: 54 | Enabled: true 55 | 56 | Capybara/VisibilityMatcher: 57 | Enabled: true 58 | 59 | Metrics: 60 | Enabled: false 61 | 62 | Layout/CommentIndentation: 63 | Enabled: true 64 | 65 | Layout/EmptyLines: 66 | Enabled: true 67 | 68 | Layout/EmptyLinesAroundAccessModifier: 69 | Enabled: true 70 | 71 | Layout/EmptyLinesAroundClassBody: 72 | Enabled: true 73 | 74 | Layout/ExtraSpacing: 75 | Enabled: true 76 | 77 | Layout/HeredocIndentation: 78 | Enabled: true 79 | 80 | Layout/IndentationConsistency: 81 | Enabled: true 82 | 83 | Layout/IndentationWidth: 84 | Enabled: true 85 | 86 | Layout/IndentationStyle: 87 | Enabled: true 88 | 89 | Layout/TrailingWhitespace: 90 | Enabled: true 91 | 92 | Layout/TrailingEmptyLines: 93 | Enabled: true 94 | 95 | Lint/AmbiguousOperator: 96 | Enabled: true 97 | 98 | Lint/AmbiguousRegexpLiteral: 99 | Enabled: true 100 | 101 | Lint/UselessAssignment: 102 | Enabled: true 103 | 104 | Packaging/BundlerSetupInTests: 105 | Enabled: true 106 | 107 | Packaging/GemspecGit: 108 | Enabled: true 109 | 110 | Packaging/RequireHardcodingLib: 111 | Enabled: true 112 | 113 | Packaging/RequireRelativeHardcodingLib: 114 | Enabled: true 115 | 116 | Performance: 117 | Enabled: true 118 | 119 | Performance/AncestorsInclude: 120 | Enabled: false 121 | 122 | Performance/ArraySemiInfiniteRangeSlice: 123 | Enabled: false 124 | 125 | Performance/BigDecimalWithNumericArgument: 126 | Enabled: true 127 | 128 | Performance/BindCall: 129 | Enabled: true 130 | 131 | Performance/BlockGivenWithExplicitBlock: 132 | Enabled: true 133 | 134 | Performance/Caller: 135 | Enabled: true 136 | 137 | Performance/CaseWhenSplat: 138 | Enabled: true 139 | 140 | Performance/Casecmp: 141 | Enabled: false 142 | 143 | Performance/ChainArrayAllocation: 144 | Enabled: false 145 | 146 | Performance/CollectionLiteralInLoop: 147 | Enabled: true 148 | Exclude: 149 | - spec/**/* 150 | 151 | Performance/CompareWithBlock: 152 | Enabled: true 153 | 154 | Performance/ConcurrentMonotonicTime: 155 | Enabled: true 156 | 157 | Performance/ConstantRegexp: 158 | Enabled: true 159 | 160 | Performance/Count: 161 | Enabled: true 162 | 163 | Performance/DeletePrefix: 164 | Enabled: true 165 | 166 | Performance/DeleteSuffix: 167 | Enabled: true 168 | 169 | Performance/Detect: 170 | Enabled: true 171 | 172 | Performance/DoubleStartEndWith: 173 | Enabled: true 174 | IncludeActiveSupportAliases: true 175 | 176 | Performance/EndWith: 177 | Enabled: true 178 | 179 | Performance/FixedSize: 180 | Enabled: true 181 | 182 | Performance/FlatMap: 183 | Enabled: true 184 | EnabledForFlattenWithoutParams: false 185 | 186 | Performance/InefficientHashSearch: 187 | Enabled: true 188 | 189 | Performance/IoReadlines: 190 | Enabled: true 191 | 192 | Performance/MapCompact: 193 | Enabled: false 194 | 195 | Performance/MapMethodChain: 196 | Enabled: false 197 | 198 | Performance/MethodObjectAsBlock: 199 | Enabled: true 200 | 201 | Performance/OpenStruct: 202 | Enabled: true 203 | 204 | Performance/RangeInclude: 205 | Enabled: true 206 | 207 | Performance/RedundantBlockCall: 208 | Enabled: false 209 | 210 | Performance/RedundantEqualityComparisonBlock: 211 | Enabled: false 212 | 213 | Performance/RedundantMatch: 214 | Enabled: true 215 | 216 | Performance/RedundantMerge: 217 | Enabled: true 218 | MaxKeyValuePairs: 2 219 | 220 | Performance/RedundantSortBlock: 221 | Enabled: true 222 | 223 | Performance/RedundantSplitRegexpArgument: 224 | Enabled: true 225 | 226 | Performance/RedundantStringChars: 227 | Enabled: true 228 | 229 | Performance/RegexpMatch: 230 | Enabled: true 231 | 232 | Performance/ReverseEach: 233 | Enabled: true 234 | 235 | Performance/ReverseFirst: 236 | Enabled: true 237 | 238 | Performance/SelectMap: 239 | Enabled: false 240 | 241 | Performance/Size: 242 | Enabled: true 243 | 244 | Performance/SortReverse: 245 | Enabled: true 246 | 247 | Performance/Squeeze: 248 | Enabled: true 249 | 250 | Performance/StartWith: 251 | Enabled: true 252 | 253 | Performance/StringBytesize: 254 | Enabled: true 255 | 256 | Performance/StringIdentifierArgument: 257 | Enabled: true 258 | 259 | Performance/StringInclude: 260 | Enabled: true 261 | 262 | Performance/StringReplacement: 263 | Enabled: true 264 | 265 | Performance/Sum: 266 | Enabled: false 267 | 268 | Performance/TimesMap: 269 | Enabled: true 270 | 271 | Performance/UnfreezeString: 272 | Enabled: true 273 | 274 | Performance/UriDefaultParser: 275 | Enabled: true 276 | 277 | Performance/ZipWithoutBlock: 278 | Enabled: true 279 | 280 | RSpec: 281 | Enabled: true 282 | 283 | RSpec/AlignLeftLetBrace: 284 | Enabled: false 285 | 286 | RSpec/AlignRightLetBrace: 287 | Enabled: false 288 | 289 | RSpec/AnyInstance: 290 | Enabled: true 291 | 292 | RSpec/AroundBlock: 293 | Enabled: true 294 | 295 | RSpec/Be: 296 | Enabled: true 297 | 298 | RSpec/BeEmpty: 299 | Enabled: true 300 | 301 | RSpec/BeEq: 302 | Enabled: true 303 | 304 | RSpec/BeEql: 305 | Enabled: true 306 | 307 | RSpec/BeNil: 308 | Enabled: true 309 | 310 | RSpec/BeforeAfterAll: 311 | Enabled: true 312 | 313 | RSpec/ChangeByZero: 314 | Enabled: true 315 | 316 | RSpec/ClassCheck: 317 | Enabled: true 318 | 319 | RSpec/ContainExactly: 320 | Enabled: true 321 | 322 | RSpec/ContextMethod: 323 | Enabled: true 324 | 325 | RSpec/ContextWording: 326 | Enabled: true 327 | 328 | RSpec/DescribeClass: 329 | Enabled: true 330 | 331 | RSpec/DescribeMethod: 332 | Enabled: false 333 | 334 | RSpec/DescribeSymbol: 335 | Enabled: true 336 | 337 | RSpec/DescribedClass: 338 | Enabled: true 339 | 340 | RSpec/DescribedClassModuleWrapping: 341 | Enabled: true 342 | 343 | RSpec/Dialect: 344 | Enabled: true 345 | 346 | RSpec/DuplicatedMetadata: 347 | Enabled: true 348 | 349 | RSpec/EmptyExampleGroup: 350 | Enabled: true 351 | 352 | RSpec/EmptyHook: 353 | Enabled: true 354 | 355 | RSpec/EmptyLineAfterExample: 356 | Enabled: true 357 | 358 | RSpec/EmptyLineAfterExampleGroup: 359 | Enabled: true 360 | 361 | RSpec/EmptyLineAfterFinalLet: 362 | Enabled: true 363 | 364 | RSpec/EmptyLineAfterHook: 365 | Enabled: true 366 | 367 | RSpec/EmptyLineAfterSubject: 368 | Enabled: true 369 | 370 | RSpec/EmptyMetadata: 371 | Enabled: true 372 | 373 | RSpec/Eq: 374 | Enabled: true 375 | 376 | RSpec/ExampleLength: 377 | Enabled: false 378 | 379 | RSpec/ExampleWithoutDescription: 380 | Enabled: true 381 | 382 | RSpec/ExampleWording: 383 | Enabled: true 384 | 385 | RSpec/ExcessiveDocstringSpacing: 386 | Enabled: true 387 | 388 | RSpec/ExpectActual: 389 | Enabled: true 390 | 391 | RSpec/ExpectChange: 392 | Enabled: true 393 | 394 | RSpec/ExpectInHook: 395 | Enabled: true 396 | 397 | RSpec/ExpectOutput: 398 | Enabled: true 399 | 400 | RSpec/Focus: 401 | Enabled: true 402 | 403 | RSpec/HookArgument: 404 | Enabled: true 405 | 406 | RSpec/HooksBeforeExamples: 407 | Enabled: true 408 | 409 | RSpec/IdenticalEqualityAssertion: 410 | Enabled: true 411 | 412 | RSpec/ImplicitBlockExpectation: 413 | Enabled: true 414 | 415 | RSpec/ImplicitExpect: 416 | Enabled: true 417 | 418 | RSpec/ImplicitSubject: 419 | Enabled: true 420 | 421 | RSpec/IndexedLet: 422 | Enabled: true 423 | 424 | RSpec/InstanceSpy: 425 | Enabled: true 426 | 427 | RSpec/InstanceVariable: 428 | Enabled: true 429 | 430 | RSpec/IsExpectedSpecify: 431 | Enabled: true 432 | 433 | RSpec/ItBehavesLike: 434 | Enabled: true 435 | 436 | RSpec/IteratedExpectation: 437 | Enabled: true 438 | 439 | RSpec/LeadingSubject: 440 | Enabled: true 441 | 442 | RSpec/LeakyConstantDeclaration: 443 | Enabled: true 444 | 445 | RSpec/LetBeforeExamples: 446 | Enabled: true 447 | 448 | RSpec/LetSetup: 449 | Enabled: true 450 | 451 | RSpec/MatchArray: 452 | Enabled: true 453 | 454 | RSpec/MessageChain: 455 | Enabled: true 456 | 457 | RSpec/MessageExpectation: 458 | Enabled: false 459 | 460 | RSpec/MessageSpies: 461 | Enabled: false 462 | 463 | RSpec/MetadataStyle: 464 | Enabled: true 465 | 466 | RSpec/MissingExampleGroupArgument: 467 | Enabled: true 468 | 469 | RSpec/MultipleDescribes: 470 | Enabled: true 471 | 472 | RSpec/MultipleExpectations: 473 | Enabled: false 474 | 475 | RSpec/MultipleMemoizedHelpers: 476 | Enabled: true 477 | 478 | RSpec/MultipleSubjects: 479 | Enabled: true 480 | 481 | RSpec/NamedSubject: 482 | Enabled: true 483 | 484 | RSpec/NestedGroups: 485 | Enabled: false 486 | 487 | RSpec/NoExpectationExample: 488 | Enabled: true 489 | 490 | RSpec/NotToNot: 491 | Enabled: true 492 | 493 | RSpec/OverwritingSetup: 494 | Enabled: true 495 | 496 | RSpec/Pending: 497 | Enabled: true 498 | 499 | RSpec/PendingWithoutReason: 500 | Enabled: true 501 | 502 | RSpec/PredicateMatcher: 503 | Enabled: true 504 | 505 | RSpec/ReceiveCounts: 506 | Enabled: true 507 | 508 | RSpec/ReceiveMessages: 509 | Enabled: true 510 | 511 | RSpec/ReceiveNever: 512 | Enabled: true 513 | 514 | RSpec/RedundantAround: 515 | Enabled: true 516 | 517 | RSpec/RedundantPredicateMatcher: 518 | Enabled: true 519 | 520 | RSpec/RemoveConst: 521 | Enabled: true 522 | 523 | RSpec/RepeatedDescription: 524 | Enabled: true 525 | 526 | RSpec/RepeatedExample: 527 | Enabled: true 528 | 529 | RSpec/RepeatedExampleGroupBody: 530 | Enabled: true 531 | 532 | RSpec/RepeatedExampleGroupDescription: 533 | Enabled: true 534 | 535 | RSpec/RepeatedIncludeExample: 536 | Enabled: true 537 | 538 | RSpec/RepeatedSubjectCall: 539 | Enabled: true 540 | 541 | RSpec/ReturnFromStub: 542 | Enabled: true 543 | 544 | RSpec/ScatteredLet: 545 | Enabled: true 546 | 547 | RSpec/ScatteredSetup: 548 | Enabled: true 549 | 550 | RSpec/SharedContext: 551 | Enabled: true 552 | 553 | RSpec/SharedExamples: 554 | Enabled: true 555 | 556 | RSpec/SingleArgumentMessageChain: 557 | Enabled: true 558 | 559 | RSpec/SkipBlockInsideExample: 560 | Enabled: true 561 | 562 | RSpec/SortMetadata: 563 | Enabled: true 564 | 565 | RSpec/SpecFilePathFormat: 566 | Enabled: false 567 | 568 | RSpec/SpecFilePathSuffix: 569 | Enabled: true 570 | 571 | RSpec/StubbedMock: 572 | Enabled: false 573 | 574 | RSpec/SubjectDeclaration: 575 | Enabled: true 576 | 577 | RSpec/SubjectStub: 578 | Enabled: true 579 | 580 | RSpec/UnspecifiedException: 581 | Enabled: true 582 | 583 | RSpec/VariableDefinition: 584 | Enabled: true 585 | 586 | RSpec/VariableName: 587 | Enabled: true 588 | 589 | RSpec/VerifiedDoubleReference: 590 | Enabled: true 591 | 592 | RSpec/VerifiedDoubles: 593 | Enabled: true 594 | 595 | RSpec/VoidExpect: 596 | Enabled: true 597 | 598 | RSpec/Yield: 599 | Enabled: true 600 | 601 | Style/Encoding: 602 | Enabled: true 603 | 604 | Style/FrozenStringLiteralComment: 605 | Enabled: true 606 | Exclude: 607 | - bin/console 608 | - '**/*.arb' 609 | 610 | Style/HashSyntax: 611 | Enabled: true 612 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | # https://yamllint.readthedocs.io/en/stable/configuration.html 2 | extends: default 3 | ignore: | 4 | node_modules/ 5 | tmp/ 6 | vendor/ 7 | spec/**/config/database.yml 8 | rules: # https://yamllint.readthedocs.io/en/stable/rules.html 9 | comments: 10 | min-spaces-from-content: 1 11 | document-start: disable 12 | line-length: disable 13 | truthy: 14 | allowed-values: 15 | - "true" 16 | - "false" 17 | - "on" 18 | - "off" 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.2.0 [☰](https://github.com/activeadmin/arbre/compare/v2.1.0...v2.2.0) 4 | 5 | * Add some missing HTML5 elements [#655][] by [@tagliala][] 6 | 7 | ## 2.1.0 [☰](https://github.com/activeadmin/arbre/compare/v2.0.2...v2.1.0) 8 | 9 | * Add support for ViewComponent. [#644][] by [@budu][] 10 | * Remove support for Ruby `< 3.1` 11 | * Remove support for Rails `< 7.0` 12 | * Test against Rails 8.0 13 | 14 | ## 2.0.2 [☰](https://github.com/activeadmin/arbre/compare/v2.0.1...v2.0.2) 15 | 16 | * Prefer `require_relative` for internal requires. [#622][] by [@tagliala][] 17 | 18 | ## 2.0.1 [☰](https://github.com/activeadmin/arbre/compare/v2.0.0...v2.0.1) 19 | 20 | * Drop dependency on ruby2_keywords. [#578][] by [@Earlopain][] 21 | * Performance improvements in HTML generation. [#562][], [#617][] by [@tagliala][] 22 | 23 | ## 2.0.0 [☰](https://github.com/activeadmin/arbre/compare/v1.7.0...v2.0.0) 24 | 25 | * Include empty attributes in HTML output. [#543][] by [@javierjulio][] 26 | * Remove table tag defaults. [#542][] by [@javierjulio][] 27 | * Remove component CSS class name default. [#545][] by [@javierjulio][] 28 | 29 | ## 1.7.0 [☰](https://github.com/activeadmin/arbre/compare/v1.6.0...v1.7.0) 30 | 31 | * Remove upper bound dependency limits from gemspec. [#539][] by [@javierjulio][] 32 | * Allow ActiveSupport 7.1. [#537][] by [@tomascco][] 33 | 34 | ## 1.6.0 [☰](https://github.com/activeadmin/arbre/compare/v1.5.0...v1.6.0) 35 | 36 | * Drop support for Ruby 2.6. [#345][] by [@alejandroperea][] 37 | * Add 'main' to HTML5 elements. [#270][] by [@mynnx][] 38 | * Support nested attribute hashes rendered as hyphenated attributes. [#451][] [@Ikariusrb][] 39 | * Lazy-load rails interactions. [#456][] [@ngan][] 40 | 41 | ## 1.5.0 [☰](https://github.com/activeadmin/arbre/compare/v1.4.0...v1.5.0) 42 | 43 | * Avoid mutating string literals. [#292][] by [@tomgilligan][] 44 | * Allow activesupport 7.0. [#314][] by [@tagliala][] 45 | * Drop ruby 2.5 support. [#315][] by [@alejandroperea][] 46 | * Fix keyword delegation in form component. [#318][] by [@deivid-rodriguez][] 47 | 48 | ## 1.4.0 [☰](https://github.com/activeadmin/arbre/compare/v1.3.0...v1.4.0) 49 | 50 | * Allow activesupport 6.1 prereleases. [#242][] by [@deivid-rodriguez][] 51 | 52 | ## 1.3.0 [☰](https://github.com/activeadmin/arbre/compare/v1.2.1...v1.3.0) 53 | 54 | * Drop ruby 2.3 support. [#152][] by [@deivid-rodriguez][] 55 | * Drop ruby 2.4 support. [#177][] by [@deivid-rodriguez][] 56 | * Fix ruby 2.7 kwargs warnings. [#202][] and [#205][] by [@deivid-rodriguez][] 57 | 58 | ## 1.2.1 [☰](https://github.com/activeadmin/arbre/compare/v1.2.0...v1.2.1) 59 | 60 | * Revert [#64][] to fix several regressions, at the cost of reintroducing [#46][]. [#121][] by [@deivid-rodriguez][] 61 | 62 | ## 1.2.0 [☰](https://github.com/activeadmin/arbre/compare/v1.2.0.rc1...v1.2.0) 63 | 64 | _No changes_. 65 | 66 | ## 1.2.0.rc1 [☰](https://github.com/activeadmin/arbre/compare/v1.1.1...v1.2.0.rc1) 67 | 68 | * Fix deprecation warning about single arity template handlers on Rails 6. [#110][] by [@aramvisser][] 69 | * Fix rendering `link_to` with a block in a arbre template. [#64][] by [@varyonic][] 70 | * Drop support for EOL'd rubies (under 2.3). [#78][] by [@deivid-rodriguez][] 71 | 72 | ## 1.1.1 [☰](https://github.com/activeadmin/arbre/compare/v1.1.0...v1.1.1) 73 | 74 | * Use mime-types 2.x for Ruby 1.9 by [@timoschilling][] 75 | * Verify Ruby 2.3 support. [#59][] by [@dlackty][] 76 | 77 | ## 1.1.0 [☰](https://github.com/activeadmin/arbre/compare/v1.0.3...v1.1.0) 78 | 79 | * Tag option `for` sets the attribute when value is a string or symbol [#49][] by [@ramontayag][] 80 | 81 | ## 1.0.3 [☰](https://github.com/activeadmin/arbre/compare/v1.0.2...v1.0.3) 82 | 83 | * Performance improvements [#40][] by [@alexesDev][] 84 | * Added all void elements as self-closing tags [#39][] by [@OscarBarrett][] 85 | * Missing tags added [#36][] / [#39][] by [@dtaniwaki][] and [@OscarBarrett][] 86 | 87 | ## 1.0.2 [☰](https://github.com/activeadmin/arbre/compare/v1.0.1...v1.0.2) 88 | 89 | * make `Element#inspect` behave correctly in Ruby 2.0 [#16][] by [@seanlinsley][] 90 | * prevent `Arbre::Element#flatten` infinite recursion [#32][] by [@seanlinsley][] 91 | * make `find_by_class` correctly find children by class [#33][] by [@kaapa][] 92 | 93 | ## 1.0.1 [☰](https://github.com/activeadmin/arbre/compare/v1.0.0...v1.0.1) 94 | 95 | * Template handler converts to string to satisfy Rack::Lint [#6][] by [@jpmckinney][] 96 | * Fix to `Tag#add_class` when passing a string of classes to Tag build method 97 | [#4][] by [@gregbell][] 98 | * Not longer uses the default separator [#7][] by [@LTe][] 99 | 100 | ## 1.0.0 [☰](https://github.com/activeadmin/arbre/compare/v1.0.0.rc4...v1.0.0) 101 | 102 | * Added support for the use of `:for` with non Active Model objects 103 | 104 | ## 1.0.0.rc4 [☰](https://github.com/activeadmin/arbre/compare/v1.0.0.rc3...v1.0.0.rc4) 105 | 106 | * Fix issue where user could call `symbolize_keys!` on a 107 | HashWithIndifferentAccess which doesn't implement the method 108 | 109 | ## 1.0.0.rc3 [☰](https://github.com/activeadmin/arbre/compare/v1.0.0.rc2...v1.0.0.rc3) 110 | 111 | * Implemented `Arbre::HTML::Tag#default_id_for_prefix` 112 | 113 | ## 1.0.0.rc2 [☰](https://github.com/activeadmin/arbre/compare/v1.0.0.rc1...v1.0.0.rc2) 114 | 115 | * Fixed bug where Component's build methods were being rendered within the 116 | parent context. 117 | 118 | ## 1.0.0.rc1 119 | 120 | Initial release and extraction from Active Admin 121 | 122 | [#4]: https://github.com/activeadmin/arbre/issues/4 123 | [#6]: https://github.com/activeadmin/arbre/issues/6 124 | [#7]: https://github.com/activeadmin/arbre/issues/7 125 | [#16]: https://github.com/activeadmin/arbre/issues/16 126 | [#32]: https://github.com/activeadmin/arbre/issues/32 127 | [#33]: https://github.com/activeadmin/arbre/issues/33 128 | [#36]: https://github.com/activeadmin/arbre/issues/36 129 | [#39]: https://github.com/activeadmin/arbre/issues/39 130 | [#40]: https://github.com/activeadmin/arbre/issues/40 131 | [#46]: https://github.com/activeadmin/arbre/issues/46 132 | [#49]: https://github.com/activeadmin/arbre/issues/49 133 | [#59]: https://github.com/activeadmin/arbre/issues/59 134 | [#64]: https://github.com/activeadmin/arbre/pull/64 135 | [#78]: https://github.com/activeadmin/arbre/pull/78 136 | [#110]: https://github.com/activeadmin/arbre/pull/110 137 | [#121]: https://github.com/activeadmin/arbre/pull/121 138 | [#152]: https://github.com/activeadmin/arbre/pull/152 139 | [#177]: https://github.com/activeadmin/arbre/pull/177 140 | [#202]: https://github.com/activeadmin/arbre/pull/202 141 | [#205]: https://github.com/activeadmin/arbre/pull/205 142 | [#242]: https://github.com/activeadmin/arbre/pull/242 143 | [#270]: https://github.com/activeadmin/arbre/pull/270 144 | [#292]: https://github.com/activeadmin/arbre/pull/292 145 | [#314]: https://github.com/activeadmin/arbre/pull/314 146 | [#315]: https://github.com/activeadmin/arbre/pull/315 147 | [#318]: https://github.com/activeadmin/arbre/pull/318 148 | [#345]: https://github.com/activeadmin/arbre/pull/345 149 | [#451]: https://github.com/activeadmin/arbre/pull/451 150 | [#456]: https://github.com/activeadmin/arbre/pull/456 151 | [#537]: https://github.com/activeadmin/arbre/pull/537 152 | [#539]: https://github.com/activeadmin/arbre/pull/539 153 | [#542]: https://github.com/activeadmin/arbre/pull/542 154 | [#543]: https://github.com/activeadmin/arbre/pull/543 155 | [#545]: https://github.com/activeadmin/arbre/pull/545 156 | [#562]: https://github.com/activeadmin/arbre/pull/562 157 | [#578]: https://github.com/activeadmin/arbre/pull/578 158 | [#617]: https://github.com/activeadmin/arbre/pull/617 159 | [#622]: https://github.com/activeadmin/arbre/pull/622 160 | [#644]: https://github.com/activeadmin/arbre/pull/644 161 | [#655]: https://github.com/activeadmin/arbre/pull/655 162 | 163 | [@aramvisser]: https://github.com/aramvisser 164 | [@LTe]: https://github.com/LTe 165 | [@OscarBarrett]: https://github.com/OscarBarrett 166 | [@alejandroperea]: https://github.com/alejandroperea 167 | [@alexesDev]: https://github.com/alexesDev 168 | [@deivid-rodriguez]: https://github.com/deivid-rodriguez 169 | [@dlackty]: https://github.com/dlackty 170 | [@dtaniwaki]: https://github.com/dtaniwaki 171 | [@gregbell]: https://github.com/gregbell 172 | [@jpmckinney]: https://github.com/jpmckinney 173 | [@kaapa]: https://github.com/kaapa 174 | [@ramontayag]: https://github.com/ramontayag 175 | [@seanlinsley]: https://github.com/seanlinsley 176 | [@timoschilling]: https://github.com/timoschilling 177 | [@varyonic]: https://github.com/varyonic 178 | [@tagliala]: https://github.com/tagliala 179 | [@tomgilligan]: https://github.com/tomgilligan 180 | [@mynnx]: https://github.com/mynnx 181 | [@Ikariusrb]: https://github.com/Ikariusrb 182 | [@ngan]: https://github.com/ngan 183 | [@tomascco]: https://github.com/tomascco 184 | [@javierjulio]: https://github.com/javierjulio 185 | [@Earlopain]: https://github.com/Earlopain 186 | [@budu]: https://github.com/budu 187 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'http://rubygems.org' 3 | 4 | gemspec 5 | 6 | gem 'rake' 7 | 8 | group :test do 9 | gem 'rspec' 10 | gem 'simplecov', require: false 11 | gem 'simplecov-cobertura' 12 | gem 'pry' 13 | end 14 | 15 | group :rubocop do 16 | gem 'rubocop' 17 | gem 'rubocop-capybara' 18 | gem 'rubocop-packaging' 19 | gem 'rubocop-performance' 20 | gem 'rubocop-rspec' 21 | end 22 | 23 | group :rails do 24 | gem 'rails', '~> 8.0.0' 25 | gem 'rspec-rails' 26 | gem 'combustion' 27 | gem 'capybara' 28 | end 29 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | arbre (2.2.0) 5 | activesupport (>= 7.0) 6 | 7 | GEM 8 | remote: http://rubygems.org/ 9 | specs: 10 | actioncable (8.0.2) 11 | actionpack (= 8.0.2) 12 | activesupport (= 8.0.2) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | zeitwerk (~> 2.6) 16 | actionmailbox (8.0.2) 17 | actionpack (= 8.0.2) 18 | activejob (= 8.0.2) 19 | activerecord (= 8.0.2) 20 | activestorage (= 8.0.2) 21 | activesupport (= 8.0.2) 22 | mail (>= 2.8.0) 23 | actionmailer (8.0.2) 24 | actionpack (= 8.0.2) 25 | actionview (= 8.0.2) 26 | activejob (= 8.0.2) 27 | activesupport (= 8.0.2) 28 | mail (>= 2.8.0) 29 | rails-dom-testing (~> 2.2) 30 | actionpack (8.0.2) 31 | actionview (= 8.0.2) 32 | activesupport (= 8.0.2) 33 | nokogiri (>= 1.8.5) 34 | rack (>= 2.2.4) 35 | rack-session (>= 1.0.1) 36 | rack-test (>= 0.6.3) 37 | rails-dom-testing (~> 2.2) 38 | rails-html-sanitizer (~> 1.6) 39 | useragent (~> 0.16) 40 | actiontext (8.0.2) 41 | actionpack (= 8.0.2) 42 | activerecord (= 8.0.2) 43 | activestorage (= 8.0.2) 44 | activesupport (= 8.0.2) 45 | globalid (>= 0.6.0) 46 | nokogiri (>= 1.8.5) 47 | actionview (8.0.2) 48 | activesupport (= 8.0.2) 49 | builder (~> 3.1) 50 | erubi (~> 1.11) 51 | rails-dom-testing (~> 2.2) 52 | rails-html-sanitizer (~> 1.6) 53 | activejob (8.0.2) 54 | activesupport (= 8.0.2) 55 | globalid (>= 0.3.6) 56 | activemodel (8.0.2) 57 | activesupport (= 8.0.2) 58 | activerecord (8.0.2) 59 | activemodel (= 8.0.2) 60 | activesupport (= 8.0.2) 61 | timeout (>= 0.4.0) 62 | activestorage (8.0.2) 63 | actionpack (= 8.0.2) 64 | activejob (= 8.0.2) 65 | activerecord (= 8.0.2) 66 | activesupport (= 8.0.2) 67 | marcel (~> 1.0) 68 | activesupport (8.0.2) 69 | base64 70 | benchmark (>= 0.3) 71 | bigdecimal 72 | concurrent-ruby (~> 1.0, >= 1.3.1) 73 | connection_pool (>= 2.2.5) 74 | drb 75 | i18n (>= 1.6, < 2) 76 | logger (>= 1.4.2) 77 | minitest (>= 5.1) 78 | securerandom (>= 0.3) 79 | tzinfo (~> 2.0, >= 2.0.5) 80 | uri (>= 0.13.1) 81 | addressable (2.8.7) 82 | public_suffix (>= 2.0.2, < 7.0) 83 | ast (2.4.3) 84 | base64 (0.2.0) 85 | benchmark (0.4.0) 86 | bigdecimal (3.1.9) 87 | builder (3.3.0) 88 | capybara (3.40.0) 89 | addressable 90 | matrix 91 | mini_mime (>= 0.1.3) 92 | nokogiri (~> 1.11) 93 | rack (>= 1.6.0) 94 | rack-test (>= 0.6.3) 95 | regexp_parser (>= 1.5, < 3.0) 96 | xpath (~> 3.2) 97 | coderay (1.1.3) 98 | combustion (1.5.0) 99 | activesupport (>= 3.0.0) 100 | railties (>= 3.0.0) 101 | thor (>= 0.14.6) 102 | concurrent-ruby (1.3.5) 103 | connection_pool (2.5.3) 104 | crass (1.0.6) 105 | date (3.4.1) 106 | diff-lcs (1.6.1) 107 | docile (1.4.1) 108 | drb (2.2.1) 109 | erubi (1.13.1) 110 | globalid (1.2.1) 111 | activesupport (>= 6.1) 112 | i18n (1.14.7) 113 | concurrent-ruby (~> 1.0) 114 | io-console (0.8.0) 115 | irb (1.15.2) 116 | pp (>= 0.6.0) 117 | rdoc (>= 4.0.0) 118 | reline (>= 0.4.2) 119 | json (2.11.3) 120 | language_server-protocol (3.17.0.4) 121 | lint_roller (1.1.0) 122 | logger (1.7.0) 123 | loofah (2.24.0) 124 | crass (~> 1.0.2) 125 | nokogiri (>= 1.12.0) 126 | mail (2.8.1) 127 | mini_mime (>= 0.1.1) 128 | net-imap 129 | net-pop 130 | net-smtp 131 | marcel (1.0.4) 132 | matrix (0.4.2) 133 | method_source (1.1.0) 134 | mini_mime (1.1.5) 135 | mini_portile2 (2.8.8) 136 | minitest (5.25.5) 137 | net-imap (0.5.7) 138 | date 139 | net-protocol 140 | net-pop (0.1.2) 141 | net-protocol 142 | net-protocol (0.2.2) 143 | timeout 144 | net-smtp (0.5.1) 145 | net-protocol 146 | nio4r (2.7.4) 147 | nokogiri (1.18.8) 148 | mini_portile2 (~> 2.8.2) 149 | racc (~> 1.4) 150 | nokogiri (1.18.8-aarch64-linux-gnu) 151 | racc (~> 1.4) 152 | nokogiri (1.18.8-arm64-darwin) 153 | racc (~> 1.4) 154 | nokogiri (1.18.8-x86_64-darwin) 155 | racc (~> 1.4) 156 | nokogiri (1.18.8-x86_64-linux-gnu) 157 | racc (~> 1.4) 158 | parallel (1.27.0) 159 | parser (3.3.8.0) 160 | ast (~> 2.4.1) 161 | racc 162 | pp (0.6.2) 163 | prettyprint 164 | prettyprint (0.2.0) 165 | prism (1.4.0) 166 | pry (0.15.2) 167 | coderay (~> 1.1) 168 | method_source (~> 1.0) 169 | psych (5.2.3) 170 | date 171 | stringio 172 | public_suffix (6.0.1) 173 | racc (1.8.1) 174 | rack (3.1.13) 175 | rack-session (2.1.0) 176 | base64 (>= 0.1.0) 177 | rack (>= 3.0.0) 178 | rack-test (2.2.0) 179 | rack (>= 1.3) 180 | rackup (2.2.1) 181 | rack (>= 3) 182 | rails (8.0.2) 183 | actioncable (= 8.0.2) 184 | actionmailbox (= 8.0.2) 185 | actionmailer (= 8.0.2) 186 | actionpack (= 8.0.2) 187 | actiontext (= 8.0.2) 188 | actionview (= 8.0.2) 189 | activejob (= 8.0.2) 190 | activemodel (= 8.0.2) 191 | activerecord (= 8.0.2) 192 | activestorage (= 8.0.2) 193 | activesupport (= 8.0.2) 194 | bundler (>= 1.15.0) 195 | railties (= 8.0.2) 196 | rails-dom-testing (2.2.0) 197 | activesupport (>= 5.0.0) 198 | minitest 199 | nokogiri (>= 1.6) 200 | rails-html-sanitizer (1.6.2) 201 | loofah (~> 2.21) 202 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 203 | railties (8.0.2) 204 | actionpack (= 8.0.2) 205 | activesupport (= 8.0.2) 206 | irb (~> 1.13) 207 | rackup (>= 1.0.0) 208 | rake (>= 12.2) 209 | thor (~> 1.0, >= 1.2.2) 210 | zeitwerk (~> 2.6) 211 | rainbow (3.1.1) 212 | rake (13.2.1) 213 | rdoc (6.13.1) 214 | psych (>= 4.0.0) 215 | regexp_parser (2.10.0) 216 | reline (0.6.1) 217 | io-console (~> 0.5) 218 | rexml (3.4.1) 219 | rspec (3.13.0) 220 | rspec-core (~> 3.13.0) 221 | rspec-expectations (~> 3.13.0) 222 | rspec-mocks (~> 3.13.0) 223 | rspec-core (3.13.3) 224 | rspec-support (~> 3.13.0) 225 | rspec-expectations (3.13.3) 226 | diff-lcs (>= 1.2.0, < 2.0) 227 | rspec-support (~> 3.13.0) 228 | rspec-mocks (3.13.2) 229 | diff-lcs (>= 1.2.0, < 2.0) 230 | rspec-support (~> 3.13.0) 231 | rspec-rails (7.1.1) 232 | actionpack (>= 7.0) 233 | activesupport (>= 7.0) 234 | railties (>= 7.0) 235 | rspec-core (~> 3.13) 236 | rspec-expectations (~> 3.13) 237 | rspec-mocks (~> 3.13) 238 | rspec-support (~> 3.13) 239 | rspec-support (3.13.2) 240 | rubocop (1.75.4) 241 | json (~> 2.3) 242 | language_server-protocol (~> 3.17.0.2) 243 | lint_roller (~> 1.1.0) 244 | parallel (~> 1.10) 245 | parser (>= 3.3.0.2) 246 | rainbow (>= 2.2.2, < 4.0) 247 | regexp_parser (>= 2.9.3, < 3.0) 248 | rubocop-ast (>= 1.44.0, < 2.0) 249 | ruby-progressbar (~> 1.7) 250 | unicode-display_width (>= 2.4.0, < 4.0) 251 | rubocop-ast (1.44.1) 252 | parser (>= 3.3.7.2) 253 | prism (~> 1.4) 254 | rubocop-capybara (2.22.1) 255 | lint_roller (~> 1.1) 256 | rubocop (~> 1.72, >= 1.72.1) 257 | rubocop-packaging (0.6.0) 258 | lint_roller (~> 1.1.0) 259 | rubocop (>= 1.72.1, < 2.0) 260 | rubocop-performance (1.25.0) 261 | lint_roller (~> 1.1) 262 | rubocop (>= 1.75.0, < 2.0) 263 | rubocop-ast (>= 1.38.0, < 2.0) 264 | rubocop-rspec (3.6.0) 265 | lint_roller (~> 1.1) 266 | rubocop (~> 1.72, >= 1.72.1) 267 | ruby-progressbar (1.13.0) 268 | securerandom (0.4.1) 269 | simplecov (0.22.0) 270 | docile (~> 1.1) 271 | simplecov-html (~> 0.11) 272 | simplecov_json_formatter (~> 0.1) 273 | simplecov-cobertura (2.1.0) 274 | rexml 275 | simplecov (~> 0.19) 276 | simplecov-html (0.13.1) 277 | simplecov_json_formatter (0.1.4) 278 | stringio (3.1.7) 279 | thor (1.3.2) 280 | timeout (0.4.3) 281 | tzinfo (2.0.6) 282 | concurrent-ruby (~> 1.0) 283 | unicode-display_width (3.1.4) 284 | unicode-emoji (~> 4.0, >= 4.0.4) 285 | unicode-emoji (4.0.4) 286 | uri (1.0.3) 287 | useragent (0.16.11) 288 | websocket-driver (0.7.7) 289 | base64 290 | websocket-extensions (>= 0.1.0) 291 | websocket-extensions (0.1.5) 292 | xpath (3.2.0) 293 | nokogiri (~> 1.8) 294 | zeitwerk (2.7.2) 295 | 296 | PLATFORMS 297 | aarch64-linux 298 | arm64-darwin 299 | ruby 300 | x86_64-darwin 301 | x86_64-linux 302 | 303 | DEPENDENCIES 304 | arbre! 305 | capybara 306 | combustion 307 | pry 308 | rails (~> 8.0.0) 309 | rake 310 | rspec 311 | rspec-rails 312 | rubocop 313 | rubocop-capybara 314 | rubocop-packaging 315 | rubocop-performance 316 | rubocop-rspec 317 | simplecov 318 | simplecov-cobertura 319 | 320 | BUNDLED WITH 321 | 2.6.8 322 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Greg Bell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arbre - HTML Views in Ruby 2 | 3 | [Arbre][docs] makes it easy to generate HTML directly in Ruby. This gem was extracted from [Active Admin](https://github.com/activeadmin/activeadmin). 4 | 5 | [![Version ][rubygems_badge]][rubygems] 6 | [![Github Actions ][actions_badge]][actions] 7 | [![Tidelift ][tidelift_badge]][tidelift] 8 | 9 | ## Goals 10 | 11 | The purpose of Arbre is to leave the view as ruby objects as long 12 | as possible. This allows OO Design to be used to implement the view layer. 13 | 14 | ## Getting started 15 | 16 | * Check out [the docs][docs]. 17 | 18 | ## Need help? 19 | 20 | Please use [StackOverflow][stackoverflow] for help requests and how-to questions. 21 | 22 | Please open GitHub issues for bugs and enhancements only, not general help requests. 23 | Please search previous issues (and Google and StackOverflow) before creating a new issue. 24 | 25 | ## Want to support us? 26 | 27 | Subscribe to [Tidelift][tidelift] to support Arbre and get licensing assurances and timely security notifications. 28 | 29 | ## Security contact information 30 | 31 | Please use the Tidelift security contact to [report a security vulnerability][Tidelift security contact]. 32 | Tidelift will coordinate the fix and disclosure. 33 | 34 | [rubygems_badge]: https://img.shields.io/gem/v/arbre.svg 35 | [rubygems]: https://rubygems.org/gems/arbre 36 | [actions_badge]: https://github.com/activeadmin/arbre/workflows/ci/badge.svg 37 | [actions]: https://github.com/activeadmin/arbre/actions 38 | [tidelift_badge]: https://tidelift.com/badges/github/activeadmin/arbre 39 | [tidelift]: https://tidelift.com/subscription/pkg/rubygems-arbre?utm_source=rubygems-arbre&utm_medium=referral&utm_campaign=readme 40 | 41 | [docs]: https://activeadmin.github.io/arbre/ 42 | [stackoverflow]: https://stackoverflow.com/questions/tagged/arbre 43 | [Tidelift security contact]: https://tidelift.com/security 44 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'bundler/gem_tasks' 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | 8 | task :console do 9 | require 'irb' 10 | require 'irb/completion' 11 | 12 | require 'pry' 13 | require 'arbre' 14 | 15 | ARGV.clear 16 | IRB.start 17 | end 18 | -------------------------------------------------------------------------------- /arbre.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "arbre/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "arbre" 7 | s.version = Arbre::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Greg Bell"] 10 | s.email = ["gregdbell@gmail.com"] 11 | s.homepage = "https://github.com/activeadmin/arbre" 12 | s.summary = %q{An Object Oriented DOM Tree in Ruby} 13 | s.description = %q{Arbre makes it easy to generate HTML directly in Ruby} 14 | s.license = "MIT" 15 | 16 | s.files = Dir['docs/**/*', 'lib/**/*', 'LICENSE'].reject { |f| File.directory?(f) } 17 | 18 | s.extra_rdoc_files = %w[CHANGELOG.md README.md] 19 | 20 | s.require_paths = ["lib"] 21 | 22 | s.metadata = { "rubygems_mfa_required" => "true" } 23 | 24 | s.required_ruby_version = '>= 3.1' 25 | 26 | s.add_dependency("activesupport", ">= 7.0") 27 | end 28 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ( set -x; bundle $@ ) 4 | 5 | for gemfile in gemfiles/*/Gemfile; do 6 | ( set -x; BUNDLE_GEMFILE="$gemfile" bundle $@ ) 7 | done 8 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "arbre" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 0.05% 6 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | gem 'github-pages' 3 | gem 'jekyll-redirect-from' 4 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - jekyll-redirect-from 3 | 4 | defaults: 5 | - 6 | values: 7 | layout: "default" 8 | -------------------------------------------------------------------------------- /docs/_includes/footer.html: -------------------------------------------------------------------------------- 1 |
3 | Copyright 2011 Greg Bell and VersaPay 4 |
5 |
6 | Tweet

7 |
8 |

9 | -------------------------------------------------------------------------------- /docs/_includes/google-analytics.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | Arbre | HTML Views in Ruby 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/_includes/toc.html: -------------------------------------------------------------------------------- 1 |
    2 |
  1. Contents
  2. 3 |
      4 |
    1. Introduction
    2. 5 |
    3. Installation
    4. 6 |
    5. Tags
    6. 7 |
    7. Components
    8. 8 |
    9. Contexts
    10. 9 | 10 |
    11. Background
    12. 11 |
    12 |
13 | -------------------------------------------------------------------------------- /docs/_includes/top-menu.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% include head.html %} 4 | 5 |
6 | {% include top-menu.html %} 7 |
8 |
9 | {% include toc.html %} 10 |
11 |
12 |
13 | {{ content }} 14 |
15 |
16 |
17 | {% include footer.html %} 18 |
19 | {% include google-analytics.html %} 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_from: /docs/documentation.html 3 | --- 4 | # Arbre 5 | HTML Views in Ruby 6 | 7 | ### Introduction 8 | 9 | Arbre is a alternate template system for [Ruby on Rails Action View](http://guides.rubyonrails.org/action_view_overview.html). 10 | Arbre expresses HTML using a Ruby DSL, which makes it similar to the [Builder](https://github.com/tenderlove/builder) gem for XML. 11 | Arbre was extracted from [Active Admin](https://activeadmin.info/). 12 | 13 | An example `index.html.arb`: 14 | 15 | ```ruby 16 | html { 17 | head { 18 | title "Welcome page" 19 | } 20 | body { 21 | para "Hello, world" 22 | } 23 | } 24 | ``` 25 | 26 | The purpose of Arbre is to leave the view as Ruby objects as long as possible, 27 | which allows an object-oriented approach including inheritance, composition, and encapsulation. 28 | 29 | ### Installation 30 | 31 | Add gem `arbre` to your `Gemfile` and `bundle install`. 32 | 33 | Arbre registers itself as a Rails template handler for files with an extension `.arb`. 34 | 35 | ### Tags 36 | 37 | Arbre DSL is composed of HTML tags. Tag attributes including `id` and HTML classes are passed as a hash parameter and the tag body is passed as a block. Most HTML5 tags are implemented, including `script`, `embed` and `video`. 38 | 39 | A special case is the paragraph tag,

, which is mapped to `para`. 40 | 41 | JavaScript can be included by using `script { raw ... }` 42 | 43 | To include text that is not immediately part of a tag use `text_node`. 44 | 45 | ### Components 46 | 47 | Arbre DSL can be extended by defining new tags composed of other, simpler tags. 48 | This provides a simpler alternative to nesting partials. 49 | The recommended approach is to subclass Arbre::Component and implement a new builder method. 50 | 51 | The builder_method defines the method that will be called to build this component 52 | when using the DSL. The arguments passed into the builder_method will be passed 53 | into the #build method for you. 54 | 55 | For example: 56 | 57 | ```ruby 58 | class Panel < Arbre::Component 59 | builder_method :panel 60 | 61 | def build(title, attributes = {}) 62 | super(attributes) 63 | 64 | h3(title, class: "panel-title") 65 | end 66 | end 67 | ``` 68 | 69 | By default, components are `div` tags. This can be overridden by redefining the `tag_name` method. 70 | 71 | Several examples of Arbre components are [included in Active Admin](https://activeadmin.info/12-arbre-components.html) 72 | 73 | ### Contexts 74 | 75 | An [Arbre::Context](http://www.rubydoc.info/gems/arbre/Arbre/Context) is an object in which Arbre DSL is interpreted, providing a root for the Ruby DOM that can be [searched and manipulated](http://www.rubydoc.info/gems/arbre/Arbre/Element). A context is automatically provided when a `.arb` template or partial is loaded. Contexts can be used when developing or testing a component. Contexts are rendered by calling to_s. 76 | 77 | ```ruby 78 | html = Arbre::Context.new do 79 | panel "Hello World", class: "panel", id: "my-panel" do 80 | span "Inside the panel" 81 | text_node "Plain text" 82 | end 83 | end 84 | 85 | puts html.to_s # => 86 | ``` 87 | 88 | ```html 89 |

90 |

Hello World

91 | Inside the panel 92 | Plain text 93 |
94 | ``` 95 | 96 | A context allows you to specify Rails template assigns, aka. 'locals' and helper methods. Templates loaded by Action View have access to all [Action View helper methods](http://guides.rubyonrails.org/action_view_overview.html#overview-of-helpers-provided-by-action-view) 97 | 98 | ### Background 99 | 100 | Similar projects include: 101 | - [Markaby](http://markaby.github.io/), written by \_why the luck stiff. 102 | - [Erector](http://erector.github.io/), developed at PivotalLabs. 103 | - [Fortitude](https://github.com/ageweke/fortitude), developed at Scribd. 104 | - [Inesita](https://inesita.fazibear.me/) (Opal) 105 | - [html_builder](https://github.com/crystal-lang/html_builder) (Crystal) 106 | 107 | -------------------------------------------------------------------------------- /docs/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | 22 | body { 23 | line-height: 1; 24 | } 25 | 26 | ol, ul { 27 | list-style: none; 28 | } 29 | 30 | table { 31 | border-collapse: collapse; 32 | border-spacing: 0; 33 | } 34 | 35 | caption, th, td { 36 | text-align: left; 37 | font-weight: normal; 38 | vertical-align: middle; 39 | } 40 | 41 | q, blockquote { 42 | quotes: none; 43 | } 44 | 45 | q:before, q:after, blockquote:before, blockquote:after { 46 | content: ""; 47 | content: none; 48 | } 49 | 50 | a img { 51 | border: none; 52 | } 53 | 54 | article, aside, details, figcaption, figure, 55 | footer, header, hgroup, menu, nav, section { 56 | display: block; 57 | } 58 | 59 | #dsl { 60 | /* Comment */ 61 | /* Error */ 62 | /* Keyword */ 63 | /* Literal */ 64 | /* Name */ 65 | /* Operator */ 66 | /* Punctuation */ 67 | /* Comment.Multiline */ 68 | /* Comment.Preproc */ 69 | /* Comment.Single */ 70 | /* Comment.Special */ 71 | /* Generic.Emph */ 72 | /* Generic.Strong */ 73 | /* Keyword.Constant */ 74 | /* Keyword.Declaration */ 75 | /* Keyword.Namespace */ 76 | /* Keyword.Pseudo */ 77 | /* Keyword.Reserved */ 78 | /* Keyword.Type */ 79 | /* Literal.Date */ 80 | /* Literal.Number */ 81 | /* Literal.String */ 82 | /* Name.Attribute */ 83 | /* Name.Builtin */ 84 | /* Name.Class */ 85 | /* Name.Constant */ 86 | /* Name.Decorator */ 87 | /* Name.Entity */ 88 | /* Name.Exception */ 89 | /* Name.Function */ 90 | /* Name.Label */ 91 | /* Name.Namespace */ 92 | /* Name.Other */ 93 | /* Name.Property */ 94 | /* Name.Tag */ 95 | /* Name.Variable */ 96 | /* Operator.Word */ 97 | /* Text.Whitespace */ 98 | /* Literal.Number.Float */ 99 | /* Literal.Number.Hex */ 100 | /* Literal.Number.Integer */ 101 | /* Literal.Number.Oct */ 102 | /* Literal.String.Backtick */ 103 | /* Literal.String.Char */ 104 | /* Literal.String.Doc */ 105 | /* Literal.String.Double */ 106 | /* Literal.String.Escape */ 107 | /* Literal.String.Heredoc */ 108 | /* Literal.String.Interpol */ 109 | /* Literal.String.Other */ 110 | /* Literal.String.Regex */ 111 | /* Literal.String.Single */ 112 | /* Literal.String.Symbol */ 113 | /* Name.Builtin.Pseudo */ 114 | /* Name.Variable.Class */ 115 | /* Name.Variable.Global */ 116 | /* Name.Variable.Instance */ 117 | /* Literal.Number.Integer.Long */ 118 | } 119 | 120 | #dsl .highlight .hll { 121 | background-color: #49483e; 122 | } 123 | 124 | #dsl .highlight { 125 | background: #272822; 126 | color: #f8f8f2; 127 | } 128 | 129 | #dsl .highlight .c { 130 | color: #75715e; 131 | } 132 | 133 | #dsl .highlight .err { 134 | color: #960050; 135 | background-color: #1e0010; 136 | } 137 | 138 | #dsl .highlight .k { 139 | color: #66d9ef; 140 | } 141 | 142 | #dsl .highlight .l { 143 | color: #ae81ff; 144 | } 145 | 146 | #dsl .highlight .n { 147 | color: #f8f8f2; 148 | } 149 | 150 | #dsl .highlight .o { 151 | color: #f92672; 152 | } 153 | 154 | #dsl .highlight .p { 155 | color: #f8f8f2; 156 | } 157 | 158 | #dsl .highlight .cm { 159 | color: #75715e; 160 | } 161 | 162 | #dsl .highlight .cp { 163 | color: #75715e; 164 | } 165 | 166 | #dsl .highlight .c1 { 167 | color: #75715e; 168 | } 169 | 170 | #dsl .highlight .cs { 171 | color: #75715e; 172 | } 173 | 174 | #dsl .highlight .ge { 175 | font-style: italic; 176 | } 177 | 178 | #dsl .highlight .gs { 179 | font-weight: bold; 180 | } 181 | 182 | #dsl .highlight .kc { 183 | color: #66d9ef; 184 | } 185 | 186 | #dsl .highlight .kd { 187 | color: #66d9ef; 188 | } 189 | 190 | #dsl .highlight .kn { 191 | color: #f92672; 192 | } 193 | 194 | #dsl .highlight .kp { 195 | color: #66d9ef; 196 | } 197 | 198 | #dsl .highlight .kr { 199 | color: #66d9ef; 200 | } 201 | 202 | #dsl .highlight .kt { 203 | color: #66d9ef; 204 | } 205 | 206 | #dsl .highlight .ld { 207 | color: #e6db74; 208 | } 209 | 210 | #dsl .highlight .m { 211 | color: #ae81ff; 212 | } 213 | 214 | #dsl .highlight .s { 215 | color: #e6db74; 216 | } 217 | 218 | #dsl .highlight .na { 219 | color: #a6e22e; 220 | } 221 | 222 | #dsl .highlight .nb { 223 | color: #f8f8f2; 224 | } 225 | 226 | #dsl .highlight .nc { 227 | color: #a6e22e; 228 | } 229 | 230 | #dsl .highlight .no { 231 | color: #66d9ef; 232 | } 233 | 234 | #dsl .highlight .nd { 235 | color: #a6e22e; 236 | } 237 | 238 | #dsl .highlight .ni { 239 | color: #f8f8f2; 240 | } 241 | 242 | #dsl .highlight .ne { 243 | color: #a6e22e; 244 | } 245 | 246 | #dsl .highlight .nf { 247 | color: #a6e22e; 248 | } 249 | 250 | #dsl .highlight .nl { 251 | color: #f8f8f2; 252 | } 253 | 254 | #dsl .highlight .nn { 255 | color: #f8f8f2; 256 | } 257 | 258 | #dsl .highlight .nx { 259 | color: #a6e22e; 260 | } 261 | 262 | #dsl .highlight .py { 263 | color: #f8f8f2; 264 | } 265 | 266 | #dsl .highlight .nt { 267 | color: #f92672; 268 | } 269 | 270 | #dsl .highlight .nv { 271 | color: #f8f8f2; 272 | } 273 | 274 | #dsl .highlight .ow { 275 | color: #f92672; 276 | } 277 | 278 | #dsl .highlight .w { 279 | color: #f8f8f2; 280 | } 281 | 282 | #dsl .highlight .mf { 283 | color: #ae81ff; 284 | } 285 | 286 | #dsl .highlight .mh { 287 | color: #ae81ff; 288 | } 289 | 290 | #dsl .highlight .mi { 291 | color: #ae81ff; 292 | } 293 | 294 | #dsl .highlight .mo { 295 | color: #ae81ff; 296 | } 297 | 298 | #dsl .highlight .sb { 299 | color: #e6db74; 300 | } 301 | 302 | #dsl .highlight .sc { 303 | color: #e6db74; 304 | } 305 | 306 | #dsl .highlight .sd { 307 | color: #e6db74; 308 | } 309 | 310 | #dsl .highlight .s2 { 311 | color: #e6db74; 312 | } 313 | 314 | #dsl .highlight .se { 315 | color: #ae81ff; 316 | } 317 | 318 | #dsl .highlight .sh { 319 | color: #e6db74; 320 | } 321 | 322 | #dsl .highlight .si { 323 | color: #e6db74; 324 | } 325 | 326 | #dsl .highlight .sx { 327 | color: #e6db74; 328 | } 329 | 330 | #dsl .highlight .sr { 331 | color: #e6db74; 332 | } 333 | 334 | #dsl .highlight .s1 { 335 | color: #e6db74; 336 | } 337 | 338 | #dsl .highlight .ss { 339 | color: #AE81FF; 340 | } 341 | 342 | #dsl .highlight .bp { 343 | color: #f8f8f2; 344 | } 345 | 346 | #dsl .highlight .vc { 347 | color: #f8f8f2; 348 | } 349 | 350 | #dsl .highlight .vg { 351 | color: #f8f8f2; 352 | } 353 | 354 | #dsl .highlight .vi { 355 | color: #f8f8f2; 356 | } 357 | 358 | #dsl .highlight .il { 359 | color: #ae81ff; 360 | } 361 | 362 | body { 363 | line-height: 1.5; 364 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; 365 | color: #333333; 366 | font-size: 75%; 367 | } 368 | 369 | h1, h2, h3, h4, h5, h6 { 370 | font-weight: normal; 371 | color: #222222; 372 | } 373 | 374 | h1 img, h2 img, h3 img, h4 img, h5 img, h6 img { 375 | margin: 0; 376 | } 377 | 378 | h1 { 379 | font-size: 3em; 380 | line-height: 1; 381 | margin-bottom: 0.50em; 382 | } 383 | 384 | h2 { 385 | font-size: 2em; 386 | margin-bottom: 0.75em; 387 | } 388 | 389 | h3 { 390 | font-size: 1.5em; 391 | line-height: 1; 392 | margin-bottom: 1.00em; 393 | } 394 | 395 | h4 { 396 | font-size: 1.2em; 397 | line-height: 1.25; 398 | margin-bottom: 1.25em; 399 | } 400 | 401 | h5 { 402 | font-size: 1em; 403 | font-weight: bold; 404 | margin-bottom: 1.50em; 405 | } 406 | 407 | h6 { 408 | font-size: 1em; 409 | font-weight: bold; 410 | } 411 | 412 | p { 413 | margin: 0 0 1.5em; 414 | } 415 | 416 | p .left { 417 | display: inline; 418 | float: left; 419 | margin: 1.5em 1.5em 1.5em 0; 420 | padding: 0; 421 | } 422 | 423 | p .right { 424 | display: inline; 425 | float: right; 426 | margin: 1.5em 0 1.5em 1.5em; 427 | padding: 0; 428 | } 429 | 430 | a { 431 | text-decoration: underline; 432 | color: #0066cc; 433 | } 434 | 435 | a:visited { 436 | color: #004c99; 437 | } 438 | 439 | a:focus { 440 | color: #0099ff; 441 | } 442 | 443 | a:hover { 444 | color: #0099ff; 445 | } 446 | 447 | a:active { 448 | color: #bf00ff; 449 | } 450 | 451 | blockquote { 452 | margin: 1.5em; 453 | color: #666666; 454 | font-style: italic; 455 | } 456 | 457 | strong, dfn { 458 | font-weight: bold; 459 | } 460 | 461 | em, dfn { 462 | font-style: italic; 463 | } 464 | 465 | sup, sub { 466 | line-height: 0; 467 | } 468 | 469 | abbr, acronym { 470 | border-bottom: 1px dotted #666666; 471 | } 472 | 473 | address { 474 | margin: 0 0 1.5em; 475 | font-style: italic; 476 | } 477 | 478 | del { 479 | color: #666666; 480 | } 481 | 482 | pre { 483 | margin: 1.5em 0; 484 | white-space: pre; 485 | } 486 | 487 | pre, code, tt { 488 | font: 1em "andale mono", "lucida console", monospace; 489 | line-height: 1.5; 490 | } 491 | 492 | li ul, li ol { 493 | margin: 0; 494 | } 495 | 496 | ul, ol { 497 | margin: 0 1.5em 1.5em 0; 498 | padding-left: 1.5em; 499 | } 500 | 501 | ul { 502 | list-style-type: disc; 503 | } 504 | 505 | ol { 506 | list-style-type: decimal; 507 | } 508 | 509 | dl { 510 | margin: 0 0 1.5em 0; 511 | } 512 | 513 | dl dt { 514 | font-weight: bold; 515 | } 516 | 517 | dd { 518 | margin-left: 1.5em; 519 | } 520 | 521 | table { 522 | margin-bottom: 1.4em; 523 | width: 100%; 524 | } 525 | 526 | th { 527 | font-weight: bold; 528 | } 529 | 530 | thead th { 531 | background: #c3d9ff; 532 | } 533 | 534 | th, td, caption { 535 | padding: 4px 10px 4px 5px; 536 | } 537 | 538 | table.striped tr:nth-child(even) td, 539 | table tr.even td { 540 | background: #e5ecf9; 541 | } 542 | 543 | tfoot { 544 | font-style: italic; 545 | } 546 | 547 | caption { 548 | background: #eeeeee; 549 | } 550 | 551 | .quiet { 552 | color: #666666; 553 | } 554 | 555 | .loud { 556 | color: #111111; 557 | } 558 | 559 | body { 560 | background: #f6f4f1; 561 | color: #676767; 562 | width: 676px; 563 | padding: 0 20px; 564 | font-size: 95%; 565 | margin: 25px auto; 566 | font-family: 'Georgia'; 567 | } 568 | 569 | body #wrapper { 570 | position: relative; 571 | } 572 | 573 | body a, body a:hover, body a:link, body a:visited { 574 | color: #407985; 575 | } 576 | 577 | body h1, body h2, body h3, body h4, body h5, body h6 { 578 | color: #595959; 579 | font-family: 'Yanone Kaffeesatz', 'Helvetica Neue', Arial, Helvetica, sans-serif; 580 | } 581 | 582 | body h1 a { 583 | background: url("../images/activeadmin.png") 0 0 no-repeat; 584 | display: block; 585 | width: 257px; 586 | height: 55px; 587 | } 588 | 589 | body h1 a span { 590 | display: none; 591 | } 592 | 593 | body #header { 594 | margin: 40px 0; 595 | } 596 | 597 | body .clear { 598 | clear: both; 599 | } 600 | 601 | body .intro { 602 | color: #595959; 603 | font-family: 'Yanone Kaffeesatz', 'Helvetica Neue', Arial, Helvetica, sans-serif; 604 | font-size: 3.2em; 605 | font-weight: 300; 606 | line-height: 1em; 607 | margin-bottom: 0.3em; 608 | padding-top: 35px; 609 | background: url("../images/divider.png") 0 0 repeat-x; 610 | } 611 | 612 | body .intro strong { 613 | font-weight: 400; 614 | } 615 | 616 | body h2 { 617 | margin: 50px 0 10px 0; 618 | padding-top: 35px; 619 | background: url("../images/divider.png") 0 0 repeat-x; 620 | font-size: 2.5em; 621 | font-weight: 200; 622 | line-height: 105%; 623 | } 624 | 625 | body h3 { 626 | margin-top: 30px; 627 | margin-bottom: 10px; 628 | font-size: 1.7em; 629 | font-weight: 300; 630 | } 631 | 632 | body #nav { 633 | font-family: 'Yanone Kaffeesatz', 'Helvetica Neue', Arial, Helvetica, sans-serif; 634 | font-weight: 400; 635 | font-size: 1.2em; 636 | text-transform: uppercase; 637 | position: absolute; 638 | right: 0; 639 | top: 20px; 640 | text-align: right; 641 | } 642 | 643 | body #nav a { 644 | text-decoration: none; 645 | color: #8b9091; 646 | margin-left: 20px; 647 | } 648 | 649 | body #features { 650 | padding-top: 0; 651 | clear: both; 652 | background: url("../images/features.png") 0 0 no-repeat; 653 | margin: 0 -35px; 654 | overflow: visible; 655 | min-height: 569px; 656 | font-size: 1.0em; 657 | line-height: 1.2em; 658 | font-weight: 300; 659 | font-family: 'Yanone Kaffeesatz', 'Helvetica Neue', Arial, Helvetica, sans-serif; 660 | } 661 | 662 | body #features #features-left { 663 | float: left; 664 | width: 150px; 665 | margin-left: -150px; 666 | } 667 | 668 | body #features #features-right { 669 | float: right; 670 | width: 150px; 671 | margin-right: -150px; 672 | } 673 | 674 | body #features h3 { 675 | font-size: 1.2em; 676 | padding-bottom: 0; 677 | margin-bottom: 5px; 678 | } 679 | 680 | body #features h3.first { 681 | margin-top: 10px; 682 | } 683 | 684 | body.with-sidebar { 685 | width: 976px; 686 | } 687 | 688 | body .toc { 689 | font-family: Helvetica, Arial, sans-serif; 690 | padding-top: 35px; 691 | width: 270px; 692 | float: right; 693 | font-size: 0.9em; 694 | background: url("../images/divider.png") top left repeat-x; 695 | } 696 | 697 | body .toc ol li { 698 | list-style: none; 699 | } 700 | 701 | body .toc a, body .toc a:link, body .toc a:hover, body .toc a:visited { 702 | text-decoration: none; 703 | color: #595959; 704 | } 705 | 706 | body .toc ol.level-1 > li { 707 | font-size: 1.0em; 708 | font-weight: bold; 709 | margin-top: 20px; 710 | } 711 | 712 | body .toc ol.level-1 > ol { 713 | padding-left: 0; 714 | font-size: 0.95em; 715 | margin: 0; 716 | } 717 | 718 | body .toc ol.level-2 > ol { 719 | display: none; 720 | } 721 | 722 | body .toc-content { 723 | width: 676px; 724 | } 725 | 726 | body #dsl { 727 | margin-top: 20px; 728 | } 729 | 730 | body #dsl .highlight { 731 | font-size: 0.82em; 732 | background: #292929 url("../images/code-header.png") 0 0 no-repeat; 733 | padding: 40px 15px 20px 15px !important; 734 | -moz-box-shadow: 0 8px 20px #444444; 735 | -webkit-box-shadow: 0 8px 20px #444444; 736 | -o-box-shadow: 0 8px 20px #444444; 737 | box-shadow: 0 8px 20px #444444; 738 | } 739 | 740 | body .getting-started { 741 | font-size: 2em; 742 | text-align: center; 743 | } 744 | 745 | body .getting-started a { 746 | margin-right: 15px; 747 | display: block; 748 | } 749 | 750 | body .getting-started-heading { 751 | text-align: center; 752 | } 753 | 754 | body .left { 755 | float: left; 756 | } 757 | 758 | body .right { 759 | float: right; 760 | } 761 | 762 | body .highlight { 763 | background-color: #333; 764 | font-family: "Droid Sans Mono", Monaco, monospace; 765 | padding: 10px 5px; 766 | font-size: 0.9em; 767 | -moz-border-radius: 2px; 768 | -webkit-border-radius: 2px; 769 | -o-border-radius: 2px; 770 | -ms-border-radius: 2px; 771 | -khtml-border-radius: 2px; 772 | border-radius: 2px; 773 | margin-bottom: 1.5em; 774 | } 775 | 776 | body .highlight > pre, body .highlight code, body .highlight span { 777 | line-height: 1.3em; 778 | margin: 0; 779 | padding: 0; 780 | } 781 | 782 | body #footer { 783 | margin-top: 50px; 784 | margin-bottom: 20px; 785 | background: url("../images/divider.png") 0 0 repeat-x; 786 | clear: both; 787 | padding-top: 20px; 788 | font-size: 0.9em; 789 | } 790 | 791 | body .post .post-date, body .post .post-meta { 792 | font-size: 0.7em; 793 | } 794 | 795 | body .post .post-date { 796 | display: inline-block; 797 | width: 100px; 798 | } 799 | 800 | body .post .post-meta { 801 | font-size: 0.6em; 802 | padding-left: 40px; 803 | } 804 | 805 | span.breadcrumb { 806 | display: block; 807 | font-size: 45%; 808 | font-weight: 200; 809 | margin: 0; 810 | padding: 0; 811 | } 812 | 813 | h2.in-docs { 814 | font-weight: 400; 815 | } 816 | 817 | .docs-content { 818 | /* Comment */ 819 | /* Error */ 820 | /* Keyword */ 821 | /* Comment.Multiline */ 822 | /* Comment.Preproc */ 823 | /* Comment.Single */ 824 | /* Comment.Special */ 825 | /* Generic.Deleted */ 826 | /* Generic.Emph */ 827 | /* Generic.Error */ 828 | /* Generic.Heading */ 829 | /* Generic.Inserted */ 830 | /* Generic.Output */ 831 | /* Generic.Prompt */ 832 | /* Generic.Strong */ 833 | /* Generic.Subheading */ 834 | /* Generic.Traceback */ 835 | /* Keyword.Constant */ 836 | /* Keyword.Declaration */ 837 | /* Keyword.Namespace */ 838 | /* Keyword.Pseudo */ 839 | /* Keyword.Reserved */ 840 | /* Keyword.Type */ 841 | /* Literal.Number */ 842 | /* Name */ 843 | /* Name */ 844 | /* Literal.String */ 845 | /* Name.Attribute */ 846 | /* Name.Builtin */ 847 | /* Name.Class */ 848 | /* Name.Constant */ 849 | /* Name.Decorator */ 850 | /* Name.Entity */ 851 | /* Name.Function */ 852 | /* Name.Namespace */ 853 | /* Name.Tag */ 854 | /* Name.Variable */ 855 | /* Operator.Word */ 856 | /* Text.Whitespace */ 857 | /* Literal.Number.Float */ 858 | /* Literal.Number.Hex */ 859 | /* Literal.Number.Integer */ 860 | /* Literal.Number.Oct */ 861 | /* Literal.String.Backtick */ 862 | /* Literal.String.Char */ 863 | /* Literal.String.Doc */ 864 | /* Literal.String.Double */ 865 | /* Literal.String.Escape */ 866 | /* Literal.String.Heredoc */ 867 | /* Literal.String.Interpol */ 868 | /* Literal.String.Other */ 869 | /* Literal.String.Regex */ 870 | /* Literal.String.Single */ 871 | /* Literal.String.Symbol */ 872 | /* Name.Builtin.Pseudo */ 873 | /* Name.Variable.Class */ 874 | /* Name.Variable.Global */ 875 | /* Name.Variable.Instance */ 876 | /* Literal.Number.Integer.Long */ 877 | } 878 | 879 | .docs-content h3 { 880 | margin-top: 50px; 881 | margin-bottom: 10px; 882 | font-size: 2em; 883 | font-weight: 400; 884 | } 885 | 886 | .docs-content h4 { 887 | font-size: 1.5em; 888 | font-weight: 400; 889 | margin-bottom: 0; 890 | } 891 | 892 | .docs-content p, .docs-content li { 893 | font-family: Helvetica, Arial, sans-serif; 894 | font-size: 0.9em; 895 | } 896 | 897 | .docs-content .highlight { 898 | font-size: 0.85em; 899 | background-color: #ece8e1; 900 | color: #000000; 901 | } 902 | 903 | .docs-content .highlight .hll { 904 | background-color: #ffffcc; 905 | } 906 | 907 | .docs-content .highlight .c { 908 | color: #aaaaaa; 909 | font-style: italic; 910 | } 911 | 912 | .docs-content .highlight .err { 913 | color: #F00000; 914 | background-color: #F0A0A0; 915 | } 916 | 917 | .docs-content .highlight .k { 918 | color: #0000aa; 919 | } 920 | 921 | .docs-content .highlight .cm { 922 | color: #aaaaaa; 923 | font-style: italic; 924 | } 925 | 926 | .docs-content .highlight .cp { 927 | color: #4c8317; 928 | } 929 | 930 | .docs-content .highlight .c1 { 931 | color: #aaaaaa; 932 | font-style: italic; 933 | } 934 | 935 | .docs-content .highlight .cs { 936 | color: #0000aa; 937 | font-style: italic; 938 | } 939 | 940 | .docs-content .highlight .gd { 941 | color: #aa0000; 942 | } 943 | 944 | .docs-content .highlight .ge { 945 | font-style: italic; 946 | } 947 | 948 | .docs-content .highlight .gr { 949 | color: #aa0000; 950 | } 951 | 952 | .docs-content .highlight .gh { 953 | color: #000080; 954 | font-weight: bold; 955 | } 956 | 957 | .docs-content .highlight .gi { 958 | color: #00aa00; 959 | } 960 | 961 | .docs-content .highlight .go { 962 | color: #888888; 963 | } 964 | 965 | .docs-content .highlight .gp { 966 | color: #555555; 967 | } 968 | 969 | .docs-content .highlight .gs { 970 | font-weight: bold; 971 | } 972 | 973 | .docs-content .highlight .gu { 974 | color: #800080; 975 | font-weight: bold; 976 | } 977 | 978 | .docs-content .highlight .gt { 979 | color: #aa0000; 980 | } 981 | 982 | .docs-content .highlight .kc { 983 | color: #0000aa; 984 | } 985 | 986 | .docs-content .highlight .kd { 987 | color: #0000aa; 988 | } 989 | 990 | .docs-content .highlight .kn { 991 | color: #0000aa; 992 | } 993 | 994 | .docs-content .highlight .kp { 995 | color: #0000aa; 996 | } 997 | 998 | .docs-content .highlight .kr { 999 | color: #0000aa; 1000 | } 1001 | 1002 | .docs-content .highlight .kt { 1003 | color: #00aaaa; 1004 | } 1005 | 1006 | .docs-content .highlight .m { 1007 | color: #009999; 1008 | } 1009 | 1010 | .docs-content .highlight .n { 1011 | color: #000000; 1012 | } 1013 | 1014 | .docs-content .highlight .p { 1015 | color: #000000; 1016 | } 1017 | 1018 | .docs-content .highlight .s { 1019 | color: #aa5500; 1020 | } 1021 | 1022 | .docs-content .highlight .na { 1023 | color: #1e90ff; 1024 | } 1025 | 1026 | .docs-content .highlight .nb { 1027 | color: #00aaaa; 1028 | } 1029 | 1030 | .docs-content .highlight .nc { 1031 | color: #00aa00; 1032 | text-decoration: underline; 1033 | } 1034 | 1035 | .docs-content .highlight .no { 1036 | color: #aa0000; 1037 | } 1038 | 1039 | .docs-content .highlight .nd { 1040 | color: #888888; 1041 | } 1042 | 1043 | .docs-content .highlight .ni { 1044 | color: #800000; 1045 | font-weight: bold; 1046 | } 1047 | 1048 | .docs-content .highlight .nf { 1049 | color: #00aa00; 1050 | } 1051 | 1052 | .docs-content .highlight .nn { 1053 | color: #00aaaa; 1054 | text-decoration: underline; 1055 | } 1056 | 1057 | .docs-content .highlight .nt { 1058 | color: #1e90ff; 1059 | font-weight: bold; 1060 | } 1061 | 1062 | .docs-content .highlight .nv { 1063 | color: #aa0000; 1064 | } 1065 | 1066 | .docs-content .highlight .ow { 1067 | color: #0000aa; 1068 | } 1069 | 1070 | .docs-content .highlight .w { 1071 | color: #bbbbbb; 1072 | } 1073 | 1074 | .docs-content .highlight .mf { 1075 | color: #009999; 1076 | } 1077 | 1078 | .docs-content .highlight .mh { 1079 | color: #009999; 1080 | } 1081 | 1082 | .docs-content .highlight .mi { 1083 | color: #009999; 1084 | } 1085 | 1086 | .docs-content .highlight .mo { 1087 | color: #009999; 1088 | } 1089 | 1090 | .docs-content .highlight .sb { 1091 | color: #aa5500; 1092 | } 1093 | 1094 | .docs-content .highlight .sc { 1095 | color: #aa5500; 1096 | } 1097 | 1098 | .docs-content .highlight .sd { 1099 | color: #aa5500; 1100 | } 1101 | 1102 | .docs-content .highlight .s2 { 1103 | color: #aa5500; 1104 | } 1105 | 1106 | .docs-content .highlight .se { 1107 | color: #aa5500; 1108 | } 1109 | 1110 | .docs-content .highlight .sh { 1111 | color: #aa5500; 1112 | } 1113 | 1114 | .docs-content .highlight .si { 1115 | color: #aa5500; 1116 | } 1117 | 1118 | .docs-content .highlight .sx { 1119 | color: #aa5500; 1120 | } 1121 | 1122 | .docs-content .highlight .sr { 1123 | color: #009999; 1124 | } 1125 | 1126 | .docs-content .highlight .s1 { 1127 | color: #aa5500; 1128 | } 1129 | 1130 | .docs-content .highlight .ss { 1131 | color: #0000aa; 1132 | } 1133 | 1134 | .docs-content .highlight .bp { 1135 | color: #00aaaa; 1136 | } 1137 | 1138 | .docs-content .highlight .vc { 1139 | color: #aa0000; 1140 | } 1141 | 1142 | .docs-content .highlight .vg { 1143 | color: #aa0000; 1144 | } 1145 | 1146 | .docs-content .highlight .vi { 1147 | color: #aa0000; 1148 | } 1149 | 1150 | .docs-content .highlight .il { 1151 | color: #009999; 1152 | } 1153 | -------------------------------------------------------------------------------- /gemfiles/rails_70/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'http://rubygems.org' 3 | 4 | gem 'rake' 5 | 6 | group :test do 7 | gem 'rspec' 8 | gem 'simplecov', require: false 9 | gem 'simplecov-cobertura' 10 | gem 'pry' 11 | end 12 | 13 | group :rails do 14 | gem 'rails', '~> 7.0.0' 15 | gem 'rspec-rails' 16 | gem 'combustion' 17 | gem 'capybara' 18 | 19 | gem "concurrent-ruby", "1.3.4" # Ref: rails/rails#54260 20 | 21 | # FIXME: relax this dependency when Ruby 3.1 support will be dropped 22 | gem "zeitwerk", "~> 2.6.18" 23 | end 24 | 25 | gemspec path: "../.." 26 | -------------------------------------------------------------------------------- /gemfiles/rails_70/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../.. 3 | specs: 4 | arbre (2.2.0) 5 | activesupport (>= 7.0) 6 | 7 | GEM 8 | remote: http://rubygems.org/ 9 | specs: 10 | actioncable (7.0.8.7) 11 | actionpack (= 7.0.8.7) 12 | activesupport (= 7.0.8.7) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | actionmailbox (7.0.8.7) 16 | actionpack (= 7.0.8.7) 17 | activejob (= 7.0.8.7) 18 | activerecord (= 7.0.8.7) 19 | activestorage (= 7.0.8.7) 20 | activesupport (= 7.0.8.7) 21 | mail (>= 2.7.1) 22 | net-imap 23 | net-pop 24 | net-smtp 25 | actionmailer (7.0.8.7) 26 | actionpack (= 7.0.8.7) 27 | actionview (= 7.0.8.7) 28 | activejob (= 7.0.8.7) 29 | activesupport (= 7.0.8.7) 30 | mail (~> 2.5, >= 2.5.4) 31 | net-imap 32 | net-pop 33 | net-smtp 34 | rails-dom-testing (~> 2.0) 35 | actionpack (7.0.8.7) 36 | actionview (= 7.0.8.7) 37 | activesupport (= 7.0.8.7) 38 | rack (~> 2.0, >= 2.2.4) 39 | rack-test (>= 0.6.3) 40 | rails-dom-testing (~> 2.0) 41 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 42 | actiontext (7.0.8.7) 43 | actionpack (= 7.0.8.7) 44 | activerecord (= 7.0.8.7) 45 | activestorage (= 7.0.8.7) 46 | activesupport (= 7.0.8.7) 47 | globalid (>= 0.6.0) 48 | nokogiri (>= 1.8.5) 49 | actionview (7.0.8.7) 50 | activesupport (= 7.0.8.7) 51 | builder (~> 3.1) 52 | erubi (~> 1.4) 53 | rails-dom-testing (~> 2.0) 54 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 55 | activejob (7.0.8.7) 56 | activesupport (= 7.0.8.7) 57 | globalid (>= 0.3.6) 58 | activemodel (7.0.8.7) 59 | activesupport (= 7.0.8.7) 60 | activerecord (7.0.8.7) 61 | activemodel (= 7.0.8.7) 62 | activesupport (= 7.0.8.7) 63 | activestorage (7.0.8.7) 64 | actionpack (= 7.0.8.7) 65 | activejob (= 7.0.8.7) 66 | activerecord (= 7.0.8.7) 67 | activesupport (= 7.0.8.7) 68 | marcel (~> 1.0) 69 | mini_mime (>= 1.1.0) 70 | activesupport (7.0.8.7) 71 | concurrent-ruby (~> 1.0, >= 1.0.2) 72 | i18n (>= 1.6, < 2) 73 | minitest (>= 5.1) 74 | tzinfo (~> 2.0) 75 | addressable (2.8.7) 76 | public_suffix (>= 2.0.2, < 7.0) 77 | base64 (0.2.0) 78 | builder (3.3.0) 79 | capybara (3.40.0) 80 | addressable 81 | matrix 82 | mini_mime (>= 0.1.3) 83 | nokogiri (~> 1.11) 84 | rack (>= 1.6.0) 85 | rack-test (>= 0.6.3) 86 | regexp_parser (>= 1.5, < 3.0) 87 | xpath (~> 3.2) 88 | coderay (1.1.3) 89 | combustion (1.5.0) 90 | activesupport (>= 3.0.0) 91 | railties (>= 3.0.0) 92 | thor (>= 0.14.6) 93 | concurrent-ruby (1.3.4) 94 | crass (1.0.6) 95 | date (3.4.1) 96 | diff-lcs (1.6.1) 97 | docile (1.4.1) 98 | erubi (1.13.1) 99 | globalid (1.2.1) 100 | activesupport (>= 6.1) 101 | i18n (1.14.7) 102 | concurrent-ruby (~> 1.0) 103 | loofah (2.24.0) 104 | crass (~> 1.0.2) 105 | nokogiri (>= 1.12.0) 106 | mail (2.8.1) 107 | mini_mime (>= 0.1.1) 108 | net-imap 109 | net-pop 110 | net-smtp 111 | marcel (1.0.4) 112 | matrix (0.4.2) 113 | method_source (1.1.0) 114 | mini_mime (1.1.5) 115 | mini_portile2 (2.8.8) 116 | minitest (5.25.5) 117 | net-imap (0.5.7) 118 | date 119 | net-protocol 120 | net-pop (0.1.2) 121 | net-protocol 122 | net-protocol (0.2.2) 123 | timeout 124 | net-smtp (0.5.1) 125 | net-protocol 126 | nio4r (2.7.4) 127 | nokogiri (1.18.8) 128 | mini_portile2 (~> 2.8.2) 129 | racc (~> 1.4) 130 | nokogiri (1.18.8-aarch64-linux-gnu) 131 | racc (~> 1.4) 132 | nokogiri (1.18.8-arm64-darwin) 133 | racc (~> 1.4) 134 | nokogiri (1.18.8-x86_64-darwin) 135 | racc (~> 1.4) 136 | nokogiri (1.18.8-x86_64-linux-gnu) 137 | racc (~> 1.4) 138 | pry (0.15.2) 139 | coderay (~> 1.1) 140 | method_source (~> 1.0) 141 | public_suffix (6.0.1) 142 | racc (1.8.1) 143 | rack (2.2.13) 144 | rack-test (2.2.0) 145 | rack (>= 1.3) 146 | rails (7.0.8.7) 147 | actioncable (= 7.0.8.7) 148 | actionmailbox (= 7.0.8.7) 149 | actionmailer (= 7.0.8.7) 150 | actionpack (= 7.0.8.7) 151 | actiontext (= 7.0.8.7) 152 | actionview (= 7.0.8.7) 153 | activejob (= 7.0.8.7) 154 | activemodel (= 7.0.8.7) 155 | activerecord (= 7.0.8.7) 156 | activestorage (= 7.0.8.7) 157 | activesupport (= 7.0.8.7) 158 | bundler (>= 1.15.0) 159 | railties (= 7.0.8.7) 160 | rails-dom-testing (2.2.0) 161 | activesupport (>= 5.0.0) 162 | minitest 163 | nokogiri (>= 1.6) 164 | rails-html-sanitizer (1.6.2) 165 | loofah (~> 2.21) 166 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 167 | railties (7.0.8.7) 168 | actionpack (= 7.0.8.7) 169 | activesupport (= 7.0.8.7) 170 | method_source 171 | rake (>= 12.2) 172 | thor (~> 1.0) 173 | zeitwerk (~> 2.5) 174 | rake (13.2.1) 175 | regexp_parser (2.10.0) 176 | rexml (3.4.1) 177 | rspec (3.13.0) 178 | rspec-core (~> 3.13.0) 179 | rspec-expectations (~> 3.13.0) 180 | rspec-mocks (~> 3.13.0) 181 | rspec-core (3.13.3) 182 | rspec-support (~> 3.13.0) 183 | rspec-expectations (3.13.3) 184 | diff-lcs (>= 1.2.0, < 2.0) 185 | rspec-support (~> 3.13.0) 186 | rspec-mocks (3.13.2) 187 | diff-lcs (>= 1.2.0, < 2.0) 188 | rspec-support (~> 3.13.0) 189 | rspec-rails (7.1.1) 190 | actionpack (>= 7.0) 191 | activesupport (>= 7.0) 192 | railties (>= 7.0) 193 | rspec-core (~> 3.13) 194 | rspec-expectations (~> 3.13) 195 | rspec-mocks (~> 3.13) 196 | rspec-support (~> 3.13) 197 | rspec-support (3.13.2) 198 | simplecov (0.22.0) 199 | docile (~> 1.1) 200 | simplecov-html (~> 0.11) 201 | simplecov_json_formatter (~> 0.1) 202 | simplecov-cobertura (2.1.0) 203 | rexml 204 | simplecov (~> 0.19) 205 | simplecov-html (0.13.1) 206 | simplecov_json_formatter (0.1.4) 207 | thor (1.3.2) 208 | timeout (0.4.3) 209 | tzinfo (2.0.6) 210 | concurrent-ruby (~> 1.0) 211 | websocket-driver (0.7.7) 212 | base64 213 | websocket-extensions (>= 0.1.0) 214 | websocket-extensions (0.1.5) 215 | xpath (3.2.0) 216 | nokogiri (~> 1.8) 217 | zeitwerk (2.6.18) 218 | 219 | PLATFORMS 220 | aarch64-linux 221 | arm64-darwin 222 | ruby 223 | x86_64-darwin 224 | x86_64-linux 225 | 226 | DEPENDENCIES 227 | arbre! 228 | capybara 229 | combustion 230 | concurrent-ruby (= 1.3.4) 231 | pry 232 | rails (~> 7.0.0) 233 | rake 234 | rspec 235 | rspec-rails 236 | simplecov 237 | simplecov-cobertura 238 | zeitwerk (~> 2.6.18) 239 | 240 | BUNDLED WITH 241 | 2.6.8 242 | -------------------------------------------------------------------------------- /gemfiles/rails_71/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'http://rubygems.org' 3 | 4 | gem 'rake' 5 | 6 | group :test do 7 | gem 'rspec' 8 | gem 'simplecov', require: false 9 | gem 'simplecov-cobertura' 10 | gem 'pry' 11 | end 12 | 13 | group :rails do 14 | gem 'rails', '~> 7.1.0' 15 | gem 'rspec-rails' 16 | gem 'combustion' 17 | gem 'capybara' 18 | 19 | # FIXME: relax this dependency when Ruby 3.1 support will be dropped 20 | gem "zeitwerk", "~> 2.6.18" 21 | end 22 | 23 | gemspec path: "../.." 24 | -------------------------------------------------------------------------------- /gemfiles/rails_71/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../.. 3 | specs: 4 | arbre (2.2.0) 5 | activesupport (>= 7.0) 6 | 7 | GEM 8 | remote: http://rubygems.org/ 9 | specs: 10 | actioncable (7.1.5.1) 11 | actionpack (= 7.1.5.1) 12 | activesupport (= 7.1.5.1) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | zeitwerk (~> 2.6) 16 | actionmailbox (7.1.5.1) 17 | actionpack (= 7.1.5.1) 18 | activejob (= 7.1.5.1) 19 | activerecord (= 7.1.5.1) 20 | activestorage (= 7.1.5.1) 21 | activesupport (= 7.1.5.1) 22 | mail (>= 2.7.1) 23 | net-imap 24 | net-pop 25 | net-smtp 26 | actionmailer (7.1.5.1) 27 | actionpack (= 7.1.5.1) 28 | actionview (= 7.1.5.1) 29 | activejob (= 7.1.5.1) 30 | activesupport (= 7.1.5.1) 31 | mail (~> 2.5, >= 2.5.4) 32 | net-imap 33 | net-pop 34 | net-smtp 35 | rails-dom-testing (~> 2.2) 36 | actionpack (7.1.5.1) 37 | actionview (= 7.1.5.1) 38 | activesupport (= 7.1.5.1) 39 | nokogiri (>= 1.8.5) 40 | racc 41 | rack (>= 2.2.4) 42 | rack-session (>= 1.0.1) 43 | rack-test (>= 0.6.3) 44 | rails-dom-testing (~> 2.2) 45 | rails-html-sanitizer (~> 1.6) 46 | actiontext (7.1.5.1) 47 | actionpack (= 7.1.5.1) 48 | activerecord (= 7.1.5.1) 49 | activestorage (= 7.1.5.1) 50 | activesupport (= 7.1.5.1) 51 | globalid (>= 0.6.0) 52 | nokogiri (>= 1.8.5) 53 | actionview (7.1.5.1) 54 | activesupport (= 7.1.5.1) 55 | builder (~> 3.1) 56 | erubi (~> 1.11) 57 | rails-dom-testing (~> 2.2) 58 | rails-html-sanitizer (~> 1.6) 59 | activejob (7.1.5.1) 60 | activesupport (= 7.1.5.1) 61 | globalid (>= 0.3.6) 62 | activemodel (7.1.5.1) 63 | activesupport (= 7.1.5.1) 64 | activerecord (7.1.5.1) 65 | activemodel (= 7.1.5.1) 66 | activesupport (= 7.1.5.1) 67 | timeout (>= 0.4.0) 68 | activestorage (7.1.5.1) 69 | actionpack (= 7.1.5.1) 70 | activejob (= 7.1.5.1) 71 | activerecord (= 7.1.5.1) 72 | activesupport (= 7.1.5.1) 73 | marcel (~> 1.0) 74 | activesupport (7.1.5.1) 75 | base64 76 | benchmark (>= 0.3) 77 | bigdecimal 78 | concurrent-ruby (~> 1.0, >= 1.0.2) 79 | connection_pool (>= 2.2.5) 80 | drb 81 | i18n (>= 1.6, < 2) 82 | logger (>= 1.4.2) 83 | minitest (>= 5.1) 84 | mutex_m 85 | securerandom (>= 0.3) 86 | tzinfo (~> 2.0) 87 | addressable (2.8.7) 88 | public_suffix (>= 2.0.2, < 7.0) 89 | base64 (0.2.0) 90 | benchmark (0.4.0) 91 | bigdecimal (3.1.9) 92 | builder (3.3.0) 93 | capybara (3.40.0) 94 | addressable 95 | matrix 96 | mini_mime (>= 0.1.3) 97 | nokogiri (~> 1.11) 98 | rack (>= 1.6.0) 99 | rack-test (>= 0.6.3) 100 | regexp_parser (>= 1.5, < 3.0) 101 | xpath (~> 3.2) 102 | coderay (1.1.3) 103 | combustion (1.5.0) 104 | activesupport (>= 3.0.0) 105 | railties (>= 3.0.0) 106 | thor (>= 0.14.6) 107 | concurrent-ruby (1.3.5) 108 | connection_pool (2.5.3) 109 | crass (1.0.6) 110 | date (3.4.1) 111 | diff-lcs (1.6.1) 112 | docile (1.4.1) 113 | drb (2.2.1) 114 | erubi (1.13.1) 115 | globalid (1.2.1) 116 | activesupport (>= 6.1) 117 | i18n (1.14.7) 118 | concurrent-ruby (~> 1.0) 119 | io-console (0.8.0) 120 | irb (1.15.2) 121 | pp (>= 0.6.0) 122 | rdoc (>= 4.0.0) 123 | reline (>= 0.4.2) 124 | logger (1.7.0) 125 | loofah (2.24.0) 126 | crass (~> 1.0.2) 127 | nokogiri (>= 1.12.0) 128 | mail (2.8.1) 129 | mini_mime (>= 0.1.1) 130 | net-imap 131 | net-pop 132 | net-smtp 133 | marcel (1.0.4) 134 | matrix (0.4.2) 135 | method_source (1.1.0) 136 | mini_mime (1.1.5) 137 | mini_portile2 (2.8.8) 138 | minitest (5.25.5) 139 | mutex_m (0.3.0) 140 | net-imap (0.5.7) 141 | date 142 | net-protocol 143 | net-pop (0.1.2) 144 | net-protocol 145 | net-protocol (0.2.2) 146 | timeout 147 | net-smtp (0.5.1) 148 | net-protocol 149 | nio4r (2.7.4) 150 | nokogiri (1.18.8) 151 | mini_portile2 (~> 2.8.2) 152 | racc (~> 1.4) 153 | nokogiri (1.18.8-aarch64-linux-gnu) 154 | racc (~> 1.4) 155 | nokogiri (1.18.8-arm64-darwin) 156 | racc (~> 1.4) 157 | nokogiri (1.18.8-x86_64-darwin) 158 | racc (~> 1.4) 159 | nokogiri (1.18.8-x86_64-linux-gnu) 160 | racc (~> 1.4) 161 | pp (0.6.2) 162 | prettyprint 163 | prettyprint (0.2.0) 164 | pry (0.15.2) 165 | coderay (~> 1.1) 166 | method_source (~> 1.0) 167 | psych (5.2.3) 168 | date 169 | stringio 170 | public_suffix (6.0.1) 171 | racc (1.8.1) 172 | rack (3.1.14) 173 | rack-session (2.1.1) 174 | base64 (>= 0.1.0) 175 | rack (>= 3.0.0) 176 | rack-test (2.2.0) 177 | rack (>= 1.3) 178 | rackup (2.2.1) 179 | rack (>= 3) 180 | rails (7.1.5.1) 181 | actioncable (= 7.1.5.1) 182 | actionmailbox (= 7.1.5.1) 183 | actionmailer (= 7.1.5.1) 184 | actionpack (= 7.1.5.1) 185 | actiontext (= 7.1.5.1) 186 | actionview (= 7.1.5.1) 187 | activejob (= 7.1.5.1) 188 | activemodel (= 7.1.5.1) 189 | activerecord (= 7.1.5.1) 190 | activestorage (= 7.1.5.1) 191 | activesupport (= 7.1.5.1) 192 | bundler (>= 1.15.0) 193 | railties (= 7.1.5.1) 194 | rails-dom-testing (2.2.0) 195 | activesupport (>= 5.0.0) 196 | minitest 197 | nokogiri (>= 1.6) 198 | rails-html-sanitizer (1.6.2) 199 | loofah (~> 2.21) 200 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 201 | railties (7.1.5.1) 202 | actionpack (= 7.1.5.1) 203 | activesupport (= 7.1.5.1) 204 | irb 205 | rackup (>= 1.0.0) 206 | rake (>= 12.2) 207 | thor (~> 1.0, >= 1.2.2) 208 | zeitwerk (~> 2.6) 209 | rake (13.2.1) 210 | rdoc (6.13.1) 211 | psych (>= 4.0.0) 212 | regexp_parser (2.10.0) 213 | reline (0.6.1) 214 | io-console (~> 0.5) 215 | rexml (3.4.1) 216 | rspec (3.13.0) 217 | rspec-core (~> 3.13.0) 218 | rspec-expectations (~> 3.13.0) 219 | rspec-mocks (~> 3.13.0) 220 | rspec-core (3.13.3) 221 | rspec-support (~> 3.13.0) 222 | rspec-expectations (3.13.3) 223 | diff-lcs (>= 1.2.0, < 2.0) 224 | rspec-support (~> 3.13.0) 225 | rspec-mocks (3.13.2) 226 | diff-lcs (>= 1.2.0, < 2.0) 227 | rspec-support (~> 3.13.0) 228 | rspec-rails (7.1.1) 229 | actionpack (>= 7.0) 230 | activesupport (>= 7.0) 231 | railties (>= 7.0) 232 | rspec-core (~> 3.13) 233 | rspec-expectations (~> 3.13) 234 | rspec-mocks (~> 3.13) 235 | rspec-support (~> 3.13) 236 | rspec-support (3.13.2) 237 | securerandom (0.4.1) 238 | simplecov (0.22.0) 239 | docile (~> 1.1) 240 | simplecov-html (~> 0.11) 241 | simplecov_json_formatter (~> 0.1) 242 | simplecov-cobertura (2.1.0) 243 | rexml 244 | simplecov (~> 0.19) 245 | simplecov-html (0.13.1) 246 | simplecov_json_formatter (0.1.4) 247 | stringio (3.1.7) 248 | thor (1.3.2) 249 | timeout (0.4.3) 250 | tzinfo (2.0.6) 251 | concurrent-ruby (~> 1.0) 252 | websocket-driver (0.7.7) 253 | base64 254 | websocket-extensions (>= 0.1.0) 255 | websocket-extensions (0.1.5) 256 | xpath (3.2.0) 257 | nokogiri (~> 1.8) 258 | zeitwerk (2.6.18) 259 | 260 | PLATFORMS 261 | aarch64-linux 262 | arm64-darwin 263 | ruby 264 | x86_64-darwin 265 | x86_64-linux 266 | 267 | DEPENDENCIES 268 | arbre! 269 | capybara 270 | combustion 271 | pry 272 | rails (~> 7.1.0) 273 | rake 274 | rspec 275 | rspec-rails 276 | simplecov 277 | simplecov-cobertura 278 | zeitwerk (~> 2.6.18) 279 | 280 | BUNDLED WITH 281 | 2.6.8 282 | -------------------------------------------------------------------------------- /gemfiles/rails_72/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'http://rubygems.org' 3 | 4 | gem 'rake' 5 | 6 | group :test do 7 | gem 'rspec' 8 | gem 'simplecov', require: false 9 | gem 'simplecov-cobertura' 10 | gem 'pry' 11 | end 12 | 13 | group :rails do 14 | gem 'rails', '~> 7.2.0' 15 | gem 'rspec-rails' 16 | gem 'combustion' 17 | gem 'capybara' 18 | 19 | # FIXME: relax this dependency when Ruby 3.1 support will be dropped 20 | gem "zeitwerk", "~> 2.6.18" 21 | end 22 | 23 | gemspec path: "../.." 24 | -------------------------------------------------------------------------------- /gemfiles/rails_72/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../.. 3 | specs: 4 | arbre (2.2.0) 5 | activesupport (>= 7.0) 6 | 7 | GEM 8 | remote: http://rubygems.org/ 9 | specs: 10 | actioncable (7.2.2.1) 11 | actionpack (= 7.2.2.1) 12 | activesupport (= 7.2.2.1) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | zeitwerk (~> 2.6) 16 | actionmailbox (7.2.2.1) 17 | actionpack (= 7.2.2.1) 18 | activejob (= 7.2.2.1) 19 | activerecord (= 7.2.2.1) 20 | activestorage (= 7.2.2.1) 21 | activesupport (= 7.2.2.1) 22 | mail (>= 2.8.0) 23 | actionmailer (7.2.2.1) 24 | actionpack (= 7.2.2.1) 25 | actionview (= 7.2.2.1) 26 | activejob (= 7.2.2.1) 27 | activesupport (= 7.2.2.1) 28 | mail (>= 2.8.0) 29 | rails-dom-testing (~> 2.2) 30 | actionpack (7.2.2.1) 31 | actionview (= 7.2.2.1) 32 | activesupport (= 7.2.2.1) 33 | nokogiri (>= 1.8.5) 34 | racc 35 | rack (>= 2.2.4, < 3.2) 36 | rack-session (>= 1.0.1) 37 | rack-test (>= 0.6.3) 38 | rails-dom-testing (~> 2.2) 39 | rails-html-sanitizer (~> 1.6) 40 | useragent (~> 0.16) 41 | actiontext (7.2.2.1) 42 | actionpack (= 7.2.2.1) 43 | activerecord (= 7.2.2.1) 44 | activestorage (= 7.2.2.1) 45 | activesupport (= 7.2.2.1) 46 | globalid (>= 0.6.0) 47 | nokogiri (>= 1.8.5) 48 | actionview (7.2.2.1) 49 | activesupport (= 7.2.2.1) 50 | builder (~> 3.1) 51 | erubi (~> 1.11) 52 | rails-dom-testing (~> 2.2) 53 | rails-html-sanitizer (~> 1.6) 54 | activejob (7.2.2.1) 55 | activesupport (= 7.2.2.1) 56 | globalid (>= 0.3.6) 57 | activemodel (7.2.2.1) 58 | activesupport (= 7.2.2.1) 59 | activerecord (7.2.2.1) 60 | activemodel (= 7.2.2.1) 61 | activesupport (= 7.2.2.1) 62 | timeout (>= 0.4.0) 63 | activestorage (7.2.2.1) 64 | actionpack (= 7.2.2.1) 65 | activejob (= 7.2.2.1) 66 | activerecord (= 7.2.2.1) 67 | activesupport (= 7.2.2.1) 68 | marcel (~> 1.0) 69 | activesupport (7.2.2.1) 70 | base64 71 | benchmark (>= 0.3) 72 | bigdecimal 73 | concurrent-ruby (~> 1.0, >= 1.3.1) 74 | connection_pool (>= 2.2.5) 75 | drb 76 | i18n (>= 1.6, < 2) 77 | logger (>= 1.4.2) 78 | minitest (>= 5.1) 79 | securerandom (>= 0.3) 80 | tzinfo (~> 2.0, >= 2.0.5) 81 | addressable (2.8.7) 82 | public_suffix (>= 2.0.2, < 7.0) 83 | base64 (0.2.0) 84 | benchmark (0.4.0) 85 | bigdecimal (3.1.9) 86 | builder (3.3.0) 87 | capybara (3.40.0) 88 | addressable 89 | matrix 90 | mini_mime (>= 0.1.3) 91 | nokogiri (~> 1.11) 92 | rack (>= 1.6.0) 93 | rack-test (>= 0.6.3) 94 | regexp_parser (>= 1.5, < 3.0) 95 | xpath (~> 3.2) 96 | coderay (1.1.3) 97 | combustion (1.5.0) 98 | activesupport (>= 3.0.0) 99 | railties (>= 3.0.0) 100 | thor (>= 0.14.6) 101 | concurrent-ruby (1.3.5) 102 | connection_pool (2.5.3) 103 | crass (1.0.6) 104 | date (3.4.1) 105 | diff-lcs (1.6.1) 106 | docile (1.4.1) 107 | drb (2.2.1) 108 | erubi (1.13.1) 109 | globalid (1.2.1) 110 | activesupport (>= 6.1) 111 | i18n (1.14.7) 112 | concurrent-ruby (~> 1.0) 113 | io-console (0.8.0) 114 | irb (1.15.2) 115 | pp (>= 0.6.0) 116 | rdoc (>= 4.0.0) 117 | reline (>= 0.4.2) 118 | logger (1.7.0) 119 | loofah (2.24.0) 120 | crass (~> 1.0.2) 121 | nokogiri (>= 1.12.0) 122 | mail (2.8.1) 123 | mini_mime (>= 0.1.1) 124 | net-imap 125 | net-pop 126 | net-smtp 127 | marcel (1.0.4) 128 | matrix (0.4.2) 129 | method_source (1.1.0) 130 | mini_mime (1.1.5) 131 | mini_portile2 (2.8.8) 132 | minitest (5.25.5) 133 | net-imap (0.5.7) 134 | date 135 | net-protocol 136 | net-pop (0.1.2) 137 | net-protocol 138 | net-protocol (0.2.2) 139 | timeout 140 | net-smtp (0.5.1) 141 | net-protocol 142 | nio4r (2.7.4) 143 | nokogiri (1.18.8) 144 | mini_portile2 (~> 2.8.2) 145 | racc (~> 1.4) 146 | nokogiri (1.18.8-aarch64-linux-gnu) 147 | racc (~> 1.4) 148 | nokogiri (1.18.8-arm64-darwin) 149 | racc (~> 1.4) 150 | nokogiri (1.18.8-x86_64-darwin) 151 | racc (~> 1.4) 152 | nokogiri (1.18.8-x86_64-linux-gnu) 153 | racc (~> 1.4) 154 | pp (0.6.2) 155 | prettyprint 156 | prettyprint (0.2.0) 157 | pry (0.15.2) 158 | coderay (~> 1.1) 159 | method_source (~> 1.0) 160 | psych (5.2.3) 161 | date 162 | stringio 163 | public_suffix (6.0.1) 164 | racc (1.8.1) 165 | rack (3.1.13) 166 | rack-session (2.1.0) 167 | base64 (>= 0.1.0) 168 | rack (>= 3.0.0) 169 | rack-test (2.2.0) 170 | rack (>= 1.3) 171 | rackup (2.2.1) 172 | rack (>= 3) 173 | rails (7.2.2.1) 174 | actioncable (= 7.2.2.1) 175 | actionmailbox (= 7.2.2.1) 176 | actionmailer (= 7.2.2.1) 177 | actionpack (= 7.2.2.1) 178 | actiontext (= 7.2.2.1) 179 | actionview (= 7.2.2.1) 180 | activejob (= 7.2.2.1) 181 | activemodel (= 7.2.2.1) 182 | activerecord (= 7.2.2.1) 183 | activestorage (= 7.2.2.1) 184 | activesupport (= 7.2.2.1) 185 | bundler (>= 1.15.0) 186 | railties (= 7.2.2.1) 187 | rails-dom-testing (2.2.0) 188 | activesupport (>= 5.0.0) 189 | minitest 190 | nokogiri (>= 1.6) 191 | rails-html-sanitizer (1.6.2) 192 | loofah (~> 2.21) 193 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 194 | railties (7.2.2.1) 195 | actionpack (= 7.2.2.1) 196 | activesupport (= 7.2.2.1) 197 | irb (~> 1.13) 198 | rackup (>= 1.0.0) 199 | rake (>= 12.2) 200 | thor (~> 1.0, >= 1.2.2) 201 | zeitwerk (~> 2.6) 202 | rake (13.2.1) 203 | rdoc (6.13.1) 204 | psych (>= 4.0.0) 205 | regexp_parser (2.10.0) 206 | reline (0.6.1) 207 | io-console (~> 0.5) 208 | rexml (3.4.1) 209 | rspec (3.13.0) 210 | rspec-core (~> 3.13.0) 211 | rspec-expectations (~> 3.13.0) 212 | rspec-mocks (~> 3.13.0) 213 | rspec-core (3.13.3) 214 | rspec-support (~> 3.13.0) 215 | rspec-expectations (3.13.3) 216 | diff-lcs (>= 1.2.0, < 2.0) 217 | rspec-support (~> 3.13.0) 218 | rspec-mocks (3.13.2) 219 | diff-lcs (>= 1.2.0, < 2.0) 220 | rspec-support (~> 3.13.0) 221 | rspec-rails (7.1.1) 222 | actionpack (>= 7.0) 223 | activesupport (>= 7.0) 224 | railties (>= 7.0) 225 | rspec-core (~> 3.13) 226 | rspec-expectations (~> 3.13) 227 | rspec-mocks (~> 3.13) 228 | rspec-support (~> 3.13) 229 | rspec-support (3.13.2) 230 | securerandom (0.4.1) 231 | simplecov (0.22.0) 232 | docile (~> 1.1) 233 | simplecov-html (~> 0.11) 234 | simplecov_json_formatter (~> 0.1) 235 | simplecov-cobertura (2.1.0) 236 | rexml 237 | simplecov (~> 0.19) 238 | simplecov-html (0.13.1) 239 | simplecov_json_formatter (0.1.4) 240 | stringio (3.1.7) 241 | thor (1.3.2) 242 | timeout (0.4.3) 243 | tzinfo (2.0.6) 244 | concurrent-ruby (~> 1.0) 245 | useragent (0.16.11) 246 | websocket-driver (0.7.7) 247 | base64 248 | websocket-extensions (>= 0.1.0) 249 | websocket-extensions (0.1.5) 250 | xpath (3.2.0) 251 | nokogiri (~> 1.8) 252 | zeitwerk (2.6.18) 253 | 254 | PLATFORMS 255 | aarch64-linux 256 | arm64-darwin 257 | ruby 258 | x86_64-darwin 259 | x86_64-linux 260 | 261 | DEPENDENCIES 262 | arbre! 263 | capybara 264 | combustion 265 | pry 266 | rails (~> 7.2.0) 267 | rake 268 | rspec 269 | rspec-rails 270 | simplecov 271 | simplecov-cobertura 272 | zeitwerk (~> 2.6.18) 273 | 274 | BUNDLED WITH 275 | 2.6.8 276 | -------------------------------------------------------------------------------- /lib/arbre.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'active_support/core_ext/string/output_safety' 3 | require 'active_support/hash_with_indifferent_access' 4 | require 'active_support/inflector' 5 | 6 | module Arbre 7 | end 8 | 9 | require_relative 'arbre/element' 10 | require_relative 'arbre/context' 11 | require_relative 'arbre/html/attributes' 12 | require_relative 'arbre/html/class_list' 13 | require_relative 'arbre/html/tag' 14 | require_relative 'arbre/html/text_node' 15 | require_relative 'arbre/html/document' 16 | require_relative 'arbre/html/html5_elements' 17 | require_relative 'arbre/component' 18 | 19 | if defined?(Rails) 20 | require_relative 'arbre/railtie' 21 | end 22 | -------------------------------------------------------------------------------- /lib/arbre/component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | class Component < Arbre::HTML::Div 4 | # By default components render a div 5 | def tag_name 6 | 'div' 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/arbre/context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'element' 3 | 4 | module Arbre 5 | 6 | # The Arbre::Context class is the frontend for using Arbre. 7 | # 8 | # The simplest example possible: 9 | # 10 | # html = Arbre::Context.new do 11 | # h1 "Hello World" 12 | # end 13 | # 14 | # html.to_s #=> "

Hello World

" 15 | # 16 | # The contents of the block are instance eval'd within the Context 17 | # object. This means that you lose context to the outside world from 18 | # within the block. To pass local variables into the Context, use the 19 | # assigns param. 20 | # 21 | # html = Arbre::Context.new({one: 1}) do 22 | # h1 "Your number #{one}" 23 | # end 24 | # 25 | # html.to_s #=> "Your number 1" 26 | # 27 | class Context < Element 28 | # Initialize a new Arbre::Context 29 | # 30 | # @param [Hash] assigns A hash of objects that you would like to be 31 | # available as local variables within the Context 32 | # 33 | # @param [Object] helpers An object that has methods on it which will become 34 | # instance methods within the context. 35 | # 36 | # @yield [] The block that will get instance eval'd in the context 37 | def initialize(assigns = {}, helpers = nil, &block) 38 | assigns = assigns || {} 39 | @_assigns = assigns.symbolize_keys 40 | 41 | @_helpers = helpers 42 | @_current_arbre_element_buffer = [self] 43 | 44 | super(self) 45 | instance_eval(&block) if block 46 | end 47 | 48 | def arbre_context 49 | self 50 | end 51 | 52 | def assigns 53 | @_assigns 54 | end 55 | 56 | def helpers 57 | @_helpers 58 | end 59 | 60 | def indent_level 61 | # A context does not increment the indent_level 62 | super - 1 63 | end 64 | 65 | def bytesize 66 | cached_html.bytesize 67 | end 68 | alias :length :bytesize 69 | 70 | def respond_to_missing?(method, include_all) 71 | super || cached_html.respond_to?(method, include_all) 72 | end 73 | 74 | # Webservers treat Arbre::Context as a string. We override 75 | # method_missing to delegate to the string representation 76 | # of the html. 77 | ruby2_keywords def method_missing(method, *args, &block) 78 | if cached_html.respond_to? method 79 | cached_html.send method, *args, &block 80 | else 81 | super 82 | end 83 | end 84 | 85 | def current_arbre_element 86 | @_current_arbre_element_buffer.last 87 | end 88 | 89 | def with_current_arbre_element(tag) 90 | raise ArgumentError, "Can't be in the context of nil. #{@_current_arbre_element_buffer.inspect}" unless tag 91 | @_current_arbre_element_buffer.push tag 92 | yield 93 | @_current_arbre_element_buffer.pop 94 | end 95 | alias_method :within, :with_current_arbre_element 96 | 97 | private 98 | 99 | # Caches the rendered HTML so that we don't re-render just to 100 | # get the content length or to delegate a method to the HTML 101 | def cached_html 102 | if defined?(@cached_html) 103 | @cached_html 104 | else 105 | html = to_s 106 | @cached_html = html if html.length > 0 107 | html 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/arbre/element.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'arbre/element/builder_methods' 3 | require 'arbre/element/proxy' 4 | require 'arbre/element_collection' 5 | 6 | module Arbre 7 | 8 | class Element 9 | include BuilderMethods 10 | 11 | attr_reader :parent 12 | attr_reader :children, :arbre_context 13 | 14 | def initialize(arbre_context = Arbre::Context.new) 15 | @arbre_context = arbre_context 16 | @children = ElementCollection.new 17 | @parent = nil 18 | end 19 | 20 | def assigns 21 | arbre_context.assigns 22 | end 23 | 24 | def helpers 25 | arbre_context.helpers 26 | end 27 | 28 | def tag_name 29 | @tag_name ||= self.class.name.demodulize.downcase 30 | end 31 | 32 | def build(*args, &block) 33 | # Render the block passing ourselves in 34 | append_return_block(block.call(self)) if block 35 | end 36 | 37 | def add_child(child) 38 | return unless child 39 | 40 | if child.is_a?(Array) 41 | child.each{|item| add_child(item) } 42 | return @children 43 | end 44 | 45 | # If its not an element, wrap it in a TextNode 46 | unless child.is_a?(Element) 47 | child = Arbre::HTML::TextNode.from_string(child) 48 | end 49 | 50 | if child.respond_to?(:parent) 51 | # Remove the child 52 | child.parent.remove_child(child) if child.parent && child.parent != self 53 | # Set ourselves as the parent 54 | child.parent = self 55 | end 56 | 57 | @children << child 58 | end 59 | 60 | def remove_child(child) 61 | child.parent = nil if child.respond_to?(:parent=) 62 | @children.delete(child) 63 | end 64 | 65 | def <<(child) 66 | add_child(child) 67 | end 68 | 69 | def children? 70 | @children.any? 71 | end 72 | 73 | def parent=(parent) 74 | @parent = parent 75 | end 76 | 77 | def parent? 78 | !@parent.nil? 79 | end 80 | 81 | def ancestors 82 | if parent? 83 | [parent] + parent.ancestors 84 | else 85 | [] 86 | end 87 | end 88 | 89 | # TODO: Shouldn't grab whole tree 90 | def find_first_ancestor(type) 91 | ancestors.find{|a| a.is_a?(type) } 92 | end 93 | 94 | def content=(contents) 95 | clear_children! 96 | add_child(contents) 97 | end 98 | 99 | def get_elements_by_tag_name(tag_name) 100 | elements = ElementCollection.new 101 | children.each do |child| 102 | elements << child if child.tag_name == tag_name 103 | elements.concat(child.get_elements_by_tag_name(tag_name)) 104 | end 105 | elements 106 | end 107 | alias_method :find_by_tag, :get_elements_by_tag_name 108 | 109 | def get_elements_by_class_name(class_name) 110 | elements = ElementCollection.new 111 | children.each do |child| 112 | elements << child if child.class_list.include?(class_name) 113 | elements.concat(child.get_elements_by_class_name(class_name)) 114 | end 115 | elements 116 | end 117 | alias_method :find_by_class, :get_elements_by_class_name 118 | 119 | def content 120 | children.to_s 121 | end 122 | 123 | def html_safe 124 | to_s 125 | end 126 | 127 | def indent_level 128 | parent? ? parent.indent_level + 1 : 0 129 | end 130 | 131 | def each(&block) 132 | [to_s].each(&block) 133 | end 134 | 135 | def inspect 136 | to_s 137 | end 138 | 139 | def to_str 140 | to_s 141 | end 142 | 143 | def to_s 144 | content 145 | end 146 | 147 | def +(element) 148 | case element 149 | when Element, ElementCollection 150 | else 151 | element = Arbre::HTML::TextNode.from_string(element) 152 | end 153 | to_ary + element 154 | end 155 | 156 | def to_ary 157 | ElementCollection.new [Proxy.new(self)] 158 | end 159 | alias_method :to_a, :to_ary 160 | 161 | private 162 | 163 | # Resets the Elements children 164 | def clear_children! 165 | @children.clear 166 | end 167 | 168 | # Implements the method lookup chain. When you call a method that 169 | # doesn't exist, we: 170 | # 171 | # 1. Try to call the method on the current DOM context 172 | # 2. Return an assigned variable of the same name 173 | # 3. Call the method on the helper object 174 | # 4. Call super 175 | # 176 | ruby2_keywords def method_missing(name, *args, &block) 177 | if current_arbre_element.respond_to?(name) 178 | current_arbre_element.send name, *args, &block 179 | elsif assigns && assigns.has_key?(name) 180 | assigns[name] 181 | elsif helpers.respond_to?(name) 182 | helpers.send(name, *args, &block) 183 | else 184 | super 185 | end 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /lib/arbre/element/builder_methods.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | class Element 4 | module BuilderMethods 5 | 6 | def self.included(klass) 7 | klass.extend ClassMethods 8 | end 9 | 10 | module ClassMethods 11 | 12 | def builder_method(method_name) 13 | BuilderMethods.class_eval <<-EOF, __FILE__, __LINE__ 14 | def #{method_name}(*args, &block) 15 | insert_tag ::#{self.name}, *args, &block 16 | end 17 | EOF 18 | end 19 | 20 | end 21 | 22 | def build_tag(klass, *args, &block) 23 | tag = klass.new(arbre_context) 24 | tag.parent = current_arbre_element 25 | 26 | with_current_arbre_element tag do 27 | if block && block.arity > 0 28 | tag.build(*args, &block) 29 | else 30 | tag.build(*args) 31 | append_return_block(yield) if block 32 | end 33 | end 34 | 35 | tag 36 | end 37 | 38 | def insert_tag(klass, *args, &block) 39 | tag = build_tag(klass, *args, &block) 40 | current_arbre_element.add_child(tag) 41 | tag 42 | end 43 | 44 | def current_arbre_element 45 | arbre_context.current_arbre_element 46 | end 47 | 48 | def with_current_arbre_element(tag, &block) 49 | arbre_context.with_current_arbre_element(tag, &block) 50 | end 51 | alias_method :within, :with_current_arbre_element 52 | 53 | private 54 | 55 | # Appends the value to the current DOM element if there are no 56 | # existing DOM Children and it responds to #to_s 57 | def append_return_block(tag) 58 | return nil if current_arbre_element.children? 59 | 60 | if appendable_tag?(tag) 61 | current_arbre_element << Arbre::HTML::TextNode.from_string(tag.to_s) 62 | end 63 | end 64 | 65 | # Returns true if the object should be converted into a text node 66 | # and appended into the DOM. 67 | def appendable_tag?(tag) 68 | # Array.new.to_s prints out an empty array ("[]"). In 69 | # Arbre, we append the return value of blocks to the output, which 70 | # can cause empty arrays to show up within the output. To get 71 | # around this, we check if the object responds to #empty? 72 | if tag.respond_to?(:empty?) && tag.empty? 73 | false 74 | else 75 | !tag.is_a?(Arbre::Element) && tag.respond_to?(:to_s) 76 | end 77 | 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/arbre/element/proxy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | class Element 4 | class Proxy < BasicObject 5 | undef_method :== 6 | undef_method :equal? 7 | 8 | def initialize(element) 9 | @element = element 10 | end 11 | 12 | def respond_to?(method, include_all = false) 13 | if method.to_s == 'to_ary' 14 | false 15 | else 16 | super || @element.respond_to?(method, include_all) 17 | end 18 | end 19 | 20 | def method_missing(method, *args, &block) 21 | if method.to_s == 'to_ary' 22 | super 23 | else 24 | @element.__send__ method, *args, &block 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/arbre/element_collection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | 4 | # Stores a collection of Element objects 5 | class ElementCollection < Array 6 | def +(other) 7 | self.class.new(super) 8 | end 9 | 10 | def -(other) 11 | self.class.new(super) 12 | end 13 | 14 | def &(other) 15 | self.class.new(super) 16 | end 17 | 18 | def to_s 19 | self.collect do |element| 20 | element.to_s 21 | end.join('').html_safe 22 | end 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /lib/arbre/html/attributes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | module HTML 4 | 5 | class Attributes < Hash 6 | def to_s 7 | flatten_hash.compact.map do |name, value| 8 | "#{html_escape(name)}=\"#{html_escape(value)}\"" 9 | end.join ' ' 10 | end 11 | 12 | protected 13 | 14 | def flatten_hash(hash = self, old_path = [], accumulator = {}) 15 | hash.each do |key, value| 16 | path = old_path + [key] 17 | if value.is_a? Hash 18 | flatten_hash(value, path, accumulator) 19 | else 20 | accumulator[path.join('-')] = value 21 | end 22 | end 23 | accumulator 24 | end 25 | 26 | def html_escape(s) 27 | ERB::Util.html_escape(s) 28 | end 29 | end 30 | 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/arbre/html/class_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'set' 3 | 4 | module Arbre 5 | module HTML 6 | 7 | # Holds a set of classes 8 | class ClassList < Set 9 | def self.build_from_string(class_names) 10 | new.add(class_names) 11 | end 12 | 13 | def add(class_names) 14 | class_names.to_s.split(" ").each do |class_name| 15 | super(class_name) 16 | end 17 | self 18 | end 19 | alias :<< :add 20 | 21 | def to_s 22 | to_a.join(" ") 23 | end 24 | end 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/arbre/html/document.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | module HTML 4 | 5 | class Document < Tag 6 | def build(*args) 7 | super 8 | build_head 9 | build_body 10 | end 11 | 12 | def document 13 | self 14 | end 15 | 16 | def tag_name 17 | 'html' 18 | end 19 | 20 | def doctype 21 | ''.html_safe 22 | end 23 | 24 | def to_s 25 | doctype + super 26 | end 27 | 28 | protected 29 | 30 | def build_head 31 | @head = head do 32 | meta "http-equiv": "Content-type", content: "text/html; charset=utf-8" 33 | end 34 | end 35 | 36 | def build_body 37 | @body = body 38 | end 39 | end 40 | 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/arbre/html/html5_elements.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | module HTML 4 | 5 | AUTO_BUILD_ELEMENTS = [ 6 | :a, 7 | :abbr, 8 | :address, 9 | :area, 10 | :article, 11 | :aside, 12 | :audio, 13 | :b, 14 | :base, 15 | :bdi, 16 | :bdo, 17 | :blockquote, 18 | :body, 19 | :br, 20 | :button, 21 | :canvas, 22 | :caption, 23 | :cite, 24 | :code, 25 | :col, 26 | :colgroup, 27 | :command, 28 | :data, 29 | :datalist, 30 | :dd, 31 | :del, 32 | :details, 33 | :dfn, 34 | :dialog, 35 | :div, 36 | :dl, 37 | :dt, 38 | :em, 39 | :embed, 40 | :fieldset, 41 | :figcaption, 42 | :figure, 43 | :footer, 44 | :form, 45 | :h1, 46 | :h2, 47 | :h3, 48 | :h4, 49 | :h5, 50 | :h6, 51 | :head, 52 | :header, 53 | :hgroup, 54 | :hr, 55 | :html, 56 | :i, 57 | :iframe, 58 | :img, 59 | :input, 60 | :ins, 61 | :kbd, 62 | :keygen, 63 | :label, 64 | :legend, 65 | :li, 66 | :link, 67 | :main, 68 | :map, 69 | :mark, 70 | :menu, 71 | :menuitem, 72 | :meta, 73 | :meter, 74 | :nav, 75 | :noscript, 76 | :object, 77 | :ol, 78 | :optgroup, 79 | :option, 80 | :output, 81 | :param, 82 | :picture, 83 | :pre, 84 | :progress, 85 | :q, 86 | :rp, 87 | :rt, 88 | :ruby, 89 | :s, 90 | :samp, 91 | :script, 92 | :search, 93 | :section, 94 | :select, 95 | :slot, 96 | :small, 97 | :source, 98 | :span, 99 | :strong, 100 | :style, 101 | :sub, 102 | :summary, 103 | :sup, 104 | :svg, 105 | :table, 106 | :tbody, 107 | :td, 108 | :template, 109 | :textarea, 110 | :tfoot, 111 | :th, 112 | :thead, 113 | :time, 114 | :title, 115 | :tr, 116 | :track, 117 | :u, 118 | :ul, 119 | :var, 120 | :video, 121 | :wbr 122 | ] 123 | 124 | HTML5_ELEMENTS = [ :p ] + AUTO_BUILD_ELEMENTS 125 | 126 | AUTO_BUILD_ELEMENTS.each do |name| 127 | class_eval <<-EOF 128 | class #{name.to_s.capitalize} < Tag 129 | builder_method :#{name} 130 | end 131 | EOF 132 | end 133 | 134 | class P < Tag 135 | builder_method :para 136 | end 137 | 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /lib/arbre/html/tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'erb' 3 | 4 | module Arbre 5 | module HTML 6 | 7 | class Tag < Element 8 | attr_reader :attributes 9 | 10 | # See: https://html.spec.whatwg.org/multipage/syntax.html#void-elements 11 | SELF_CLOSING_ELEMENTS = [ :area, :base, :br, :col, :embed, :hr, :img, :input, :keygen, :link, 12 | :menuitem, :meta, :param, :source, :track, :wbr ] 13 | 14 | def initialize(*) 15 | super 16 | @attributes = Attributes.new 17 | end 18 | 19 | def build(*args) 20 | super 21 | attributes = extract_arguments(args) 22 | self.content = args.first if args.first 23 | 24 | for_value = attributes[:for] 25 | unless for_value.is_a?(String) || for_value.is_a?(Symbol) 26 | set_for_attribute(attributes.delete(:for)) 27 | end 28 | 29 | attributes.each do |key, value| 30 | set_attribute(key, value) 31 | end 32 | end 33 | 34 | def extract_arguments(args) 35 | if args.last.is_a?(Hash) 36 | args.pop 37 | else 38 | {} 39 | end 40 | end 41 | 42 | def set_attribute(name, value) 43 | @attributes[name.to_sym] = value 44 | end 45 | 46 | def get_attribute(name) 47 | @attributes[name.to_sym] 48 | end 49 | alias :attr :get_attribute 50 | 51 | def has_attribute?(name) 52 | @attributes.has_key?(name.to_sym) 53 | end 54 | 55 | def remove_attribute(name) 56 | @attributes.delete(name.to_sym) 57 | end 58 | 59 | def id 60 | get_attribute(:id) 61 | end 62 | 63 | # Generates and id for the object if it doesn't exist already 64 | def id! 65 | return id if id 66 | self.id = object_id.to_s 67 | id 68 | end 69 | 70 | def id=(id) 71 | set_attribute(:id, id) 72 | end 73 | 74 | def add_class(class_names) 75 | class_list.add class_names 76 | end 77 | 78 | def remove_class(class_names) 79 | class_list.delete(class_names) 80 | end 81 | 82 | # Returns a string of classes 83 | def class_names 84 | class_list.to_s 85 | end 86 | 87 | def class_list 88 | list = get_attribute(:class) 89 | 90 | case list 91 | when ClassList 92 | list 93 | when String 94 | set_attribute(:class, ClassList.build_from_string(list)) 95 | else 96 | set_attribute(:class, ClassList.new) 97 | end 98 | end 99 | 100 | def to_s 101 | indent(opening_tag, content, closing_tag).html_safe 102 | end 103 | 104 | private 105 | 106 | def opening_tag 107 | "<#{tag_name}#{attributes_html}>" 108 | end 109 | 110 | def closing_tag 111 | "" 112 | end 113 | 114 | INDENT_SIZE = 2 115 | 116 | def indent(open_tag, child_content, close_tag) 117 | spaces = ' ' * indent_level * INDENT_SIZE 118 | 119 | html = +"" 120 | 121 | if no_child? || child_is_text? 122 | if self_closing_tag? 123 | html << spaces << open_tag.sub( />$/, '/>' ) 124 | else 125 | # one line 126 | html << spaces << open_tag << child_content << close_tag 127 | end 128 | else 129 | # multiple lines 130 | html << spaces << open_tag << "\n" 131 | html << child_content # the child takes care of its own spaces 132 | html << spaces << close_tag 133 | end 134 | 135 | html << "\n" 136 | end 137 | 138 | def self_closing_tag? 139 | SELF_CLOSING_ELEMENTS.include?(tag_name.to_sym) 140 | end 141 | 142 | def no_child? 143 | children.empty? 144 | end 145 | 146 | def child_is_text? 147 | children.size == 1 && children.first.is_a?(TextNode) 148 | end 149 | 150 | def attributes_html 151 | " #{attributes}" if attributes.any? 152 | end 153 | 154 | def set_for_attribute(record) 155 | return unless record 156 | # set_attribute :id, ActionController::RecordIdentifier.dom_id(record, default_id_for_prefix) 157 | # add_class ActionController::RecordIdentifier.dom_class(record) 158 | set_attribute :id, dom_id_for(record) 159 | add_class dom_class_name_for(record) 160 | end 161 | 162 | def dom_class_name_for(record) 163 | if record.class.respond_to?(:model_name) 164 | record.class.model_name.singular 165 | else 166 | record.class.name.underscore.tr("/", "_") 167 | end 168 | end 169 | 170 | def dom_id_for(record) 171 | id = if record.respond_to?(:to_key) 172 | record.to_key 173 | elsif record.respond_to?(:id) 174 | record.id 175 | else 176 | record.object_id 177 | end 178 | 179 | [default_id_for_prefix, dom_class_name_for(record), id].compact.join("_") 180 | end 181 | 182 | def default_id_for_prefix 183 | nil 184 | end 185 | end 186 | 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /lib/arbre/html/text_node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'erb' 3 | 4 | module Arbre 5 | module HTML 6 | 7 | class TextNode < Element 8 | builder_method :text_node 9 | 10 | # Builds a text node from a string 11 | def self.from_string(string) 12 | node = new 13 | node.build(string) 14 | node 15 | end 16 | 17 | def add_child(*args) 18 | raise "TextNodes do not have children" 19 | end 20 | 21 | def build(string) 22 | @content = string 23 | end 24 | 25 | def class_list 26 | [] 27 | end 28 | 29 | def tag_name 30 | nil 31 | end 32 | 33 | def to_s 34 | ERB::Util.html_escape(@content.to_s) 35 | end 36 | end 37 | 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/arbre/rails/forms.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | module Rails 4 | module Forms 5 | 6 | class FormBuilderProxy < Arbre::Component 7 | attr_reader :form_builder 8 | 9 | # Since label and select are Arbre Elements already, we must 10 | # override it here instead of letting method_missing 11 | # deal with it 12 | def label(*args) 13 | proxy_call_to_form :label, *args 14 | end 15 | 16 | def select(*args) 17 | proxy_call_to_form :select, *args 18 | end 19 | 20 | def respond_to_missing?(method, include_all) 21 | if form_builder && form_builder.respond_to?(method, include_all) 22 | true 23 | else 24 | super 25 | end 26 | end 27 | 28 | private 29 | 30 | def proxy_call_to_form(method, *args, &block) 31 | text_node form_builder.send(method, *args, &block) 32 | end 33 | 34 | ruby2_keywords def method_missing(method, *args, &block) 35 | if form_builder && form_builder.respond_to?(method) 36 | proxy_call_to_form(method, *args, &block) 37 | else 38 | super 39 | end 40 | end 41 | end 42 | 43 | class FormForProxy < FormBuilderProxy 44 | builder_method :form_for 45 | 46 | def build(resource, form_options = {}, &block) 47 | form_string = helpers.form_for(resource, form_options) do |f| 48 | @form_builder = f 49 | end 50 | 51 | @opening_tag, @closing_tag = split_string_on(form_string, "") 52 | super(&block) 53 | end 54 | 55 | def fields_for(*args, &block) 56 | insert_tag FieldsForProxy, form_builder, *args, &block 57 | end 58 | 59 | def split_string_on(string, match) 60 | return "" unless string && match 61 | part_1 = string.split(Regexp.new("#{match}\\z")).first 62 | [part_1, match] 63 | end 64 | 65 | def opening_tag 66 | @opening_tag || "" 67 | end 68 | 69 | def closing_tag 70 | @closing_tag || "" 71 | end 72 | end 73 | 74 | class FieldsForProxy < FormBuilderProxy 75 | def build(form_builder, *args, &block) 76 | form_builder.fields_for(*args) do |f| 77 | @form_builder = f 78 | end 79 | 80 | super(&block) 81 | end 82 | 83 | def to_s 84 | children.to_s 85 | end 86 | end 87 | 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/arbre/rails/rendering.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | module Rails 4 | module Rendering 5 | 6 | def render(*args, &block) 7 | rendered = helpers.render(*args, &block) 8 | case rendered 9 | when Arbre::Context 10 | current_arbre_element.add_child rendered 11 | else 12 | text_node rendered 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/arbre/rails/template_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | module Rails 4 | class TemplateHandler 5 | def call(template, source = nil) 6 | source = template.source unless source 7 | 8 | <<-END 9 | Arbre::Context.new(assigns, self) { 10 | #{source} 11 | }.to_s 12 | END 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/arbre/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'rails/template_handler' 3 | require_relative 'rails/forms' 4 | require_relative 'rails/rendering' 5 | require 'rails' 6 | 7 | Arbre::Element.include(Arbre::Rails::Rendering) 8 | 9 | module Arbre 10 | class Railtie < ::Rails::Railtie 11 | initializer "arbre" do 12 | ActiveSupport.on_load(:action_view) do 13 | ActionView::Template.register_template_handler :arb, Arbre::Rails::TemplateHandler.new 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/arbre/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Arbre 3 | VERSION = "2.2.0" 4 | end 5 | -------------------------------------------------------------------------------- /spec/arbre/integration/html_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre do 5 | 6 | let(:helpers){ nil } 7 | let(:assigns){ {} } 8 | 9 | it "renders a single element" do 10 | expect(arbre { 11 | span "Hello World" 12 | }.to_s).to eq("Hello World\n") 13 | end 14 | 15 | it "renders a child element" do 16 | expect(arbre { 17 | span do 18 | span "Hello World" 19 | end 20 | }.to_s).to eq <<~HTML 21 | 22 | Hello World 23 | 24 | HTML 25 | end 26 | 27 | it "renders an unordered list" do 28 | expect(arbre { 29 | ul do 30 | li "First" 31 | li "Second" 32 | li "Third" 33 | end 34 | }.to_s).to eq <<~HTML 35 | 40 | HTML 41 | end 42 | 43 | it "allows local variables inside the tags" do 44 | expect(arbre { 45 | first = "First" 46 | second = "Second" 47 | ul do 48 | li first 49 | li second 50 | end 51 | }.to_s).to eq <<~HTML 52 | 56 | HTML 57 | end 58 | 59 | it "adds children and nested" do 60 | expect(arbre { 61 | div do 62 | ul 63 | li do 64 | li 65 | end 66 | end 67 | }.to_s).to eq <<~HTML 68 |
69 | 70 |
  • 71 |
  • 72 | 73 |
    74 | HTML 75 | end 76 | 77 | it "passes the element in to the block if asked for" do 78 | expect(arbre { 79 | div do |d| 80 | d.ul do 81 | li 82 | end 83 | end 84 | }.to_s).to eq <<~HTML 85 |
    86 | 89 |
    90 | HTML 91 | end 92 | 93 | it "moves content tags between parents" do 94 | expect(arbre { 95 | div do 96 | span(ul(li)) 97 | end 98 | }.to_s).to eq <<~HTML 99 |
    100 | 101 | 104 | 105 |
    106 | HTML 107 | end 108 | 109 | it "adds content to the parent if the element is passed into block" do 110 | expect(arbre { 111 | div do |d| 112 | d.id = "my-tag" 113 | ul do 114 | li 115 | end 116 | end 117 | }.to_s).to eq <<~HTML 118 |
    119 | 122 |
    123 | HTML 124 | end 125 | 126 | it "has the parent set on it" do 127 | list, item = nil 128 | arbre { 129 | list = ul do 130 | li "Hello" 131 | item = li "World" 132 | end 133 | } 134 | expect(item.parent).to eq list 135 | end 136 | 137 | it "sets a string content return value with no children" do 138 | expect(arbre { 139 | li do 140 | "Hello World" 141 | end 142 | }.to_s).to eq <<~HTML 143 |
  • Hello World
  • 144 | HTML 145 | end 146 | 147 | it "turns string return values into text nodes" do 148 | node = nil 149 | arbre { 150 | list = li do 151 | "Hello World" 152 | end 153 | node = list.children.first 154 | } 155 | expect(node).to be_a described_class::HTML::TextNode 156 | end 157 | 158 | it "does not render blank arrays" do 159 | expect(arbre { 160 | tbody do 161 | [] 162 | end 163 | }.to_s).to eq <<~HTML 164 | 165 | HTML 166 | end 167 | 168 | describe "self-closing nodes" do 169 | 170 | it "does not self-close script tags" do 171 | expect(arbre { 172 | script type: 'text/javascript' 173 | }.to_s).to eq("\n") 174 | end 175 | 176 | it "self-closes meta tags" do 177 | expect(arbre { 178 | meta content: "text/html; charset=utf-8" 179 | }.to_s).to eq("\n") 180 | end 181 | 182 | it "self-closes link tags" do 183 | expect(arbre { 184 | link rel: "stylesheet" 185 | }.to_s).to eq("\n") 186 | end 187 | 188 | described_class::HTML::Tag::SELF_CLOSING_ELEMENTS.each do |tag| 189 | it "self-closes #{tag} tags" do 190 | expect(arbre { 191 | send(tag) 192 | }.to_s).to eq("<#{tag}/>\n") 193 | end 194 | end 195 | 196 | end 197 | 198 | describe "html safe" do 199 | 200 | it "escapes the contents" do 201 | expect(arbre { 202 | span("
    ") 203 | }.to_s).to eq <<~HTML 204 | <br /> 205 | HTML 206 | end 207 | 208 | it "returns html safe strings" do 209 | expect(arbre { 210 | span("
    ") 211 | }.to_s).to be_html_safe 212 | end 213 | 214 | it "does not escape html passed in" do 215 | expect(arbre { 216 | span(span("
    ")) 217 | }.to_s).to eq <<~HTML 218 | 219 | <br /> 220 | 221 | HTML 222 | end 223 | 224 | it "escapes string contents when passed in block" do 225 | expect(arbre { 226 | span { 227 | span { 228 | "
    " 229 | } 230 | } 231 | }.to_s).to eq <<~HTML 232 | 233 | <br /> 234 | 235 | HTML 236 | end 237 | 238 | it "escapes the contents of attributes" do 239 | expect(arbre { 240 | span(class: "
    ") 241 | }.to_s).to eq <<~HTML 242 | 243 | HTML 244 | end 245 | 246 | end 247 | 248 | end 249 | -------------------------------------------------------------------------------- /spec/arbre/unit/component_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | # A mock subclass to play with 5 | class MockComponent < Arbre::Component 6 | builder_method :mock_component 7 | 8 | def build 9 | h2 "Hello World" 10 | end 11 | end 12 | 13 | describe Arbre::Component do 14 | let(:assigns) { {} } 15 | let(:helpers) { nil } 16 | let(:component_class) { MockComponent } 17 | let(:component) { component_class.new } 18 | 19 | it "is a subclass of an html div" do 20 | expect(described_class.ancestors).to include(Arbre::HTML::Div) 21 | end 22 | 23 | it "renders to a div, even as a subclass" do 24 | expect(component.tag_name).to eq('div') 25 | end 26 | 27 | it "does not have a class list" do 28 | expect(component.class_list.to_s).to eq("") 29 | expect(component.class_list.empty?).to be(true) 30 | end 31 | 32 | it "renders the object using the builder method name" do 33 | expect(arbre { 34 | mock_component 35 | }.to_s).to eq <<~HTML 36 |
    37 |

    Hello World

    38 |
    39 | HTML 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/arbre/unit/context_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::Context do 5 | 6 | let(:context) do 7 | described_class.new do 8 | h1 "札幌市北区" # Add some HTML to the context 9 | end 10 | end 11 | 12 | it "does not increment the indent_level" do 13 | expect(context.indent_level).to eq(-1) 14 | end 15 | 16 | it "returns a bytesize" do 17 | expect(context.bytesize).to eq(25) 18 | end 19 | 20 | it "returns a length" do 21 | expect(context.length).to eq(25) 22 | end 23 | 24 | it "delegates missing methods to the html string" do 25 | expect(context).to respond_to(:index) 26 | expect(context.index('<')).to eq(0) 27 | end 28 | 29 | it "uses a cached version of the HTML for method delegation" do 30 | expect(context).to receive(:to_s).once.and_return("

    札幌市北区

    ") 31 | expect(context.index('<')).to eq(0) 32 | expect(context.index('<')).to eq(0) 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /spec/arbre/unit/element_finder_methods_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::Element, "Finder Methods" do 5 | let(:assigns){ {} } 6 | let(:helpers){ {} } 7 | 8 | describe "finding elements by tag name" do 9 | 10 | it "returns 0 when no elements exist" do 11 | expect(arbre { 12 | div 13 | }.get_elements_by_tag_name("li").size).to eq(0) 14 | end 15 | 16 | it "returns a child element" do 17 | html = arbre do 18 | ul 19 | li 20 | ul 21 | end 22 | elements = html.get_elements_by_tag_name("li") 23 | expect(elements.size).to eq(1) 24 | expect(elements[0]).to be_instance_of(Arbre::HTML::Li) 25 | end 26 | 27 | it "returns multiple child elements" do 28 | html = arbre do 29 | ul 30 | li 31 | ul 32 | li 33 | end 34 | elements = html.get_elements_by_tag_name("li") 35 | expect(elements.size).to eq(2) 36 | expect(elements[0]).to be_instance_of(Arbre::HTML::Li) 37 | expect(elements[1]).to be_instance_of(Arbre::HTML::Li) 38 | end 39 | 40 | it "returns children's child elements" do 41 | html = arbre do 42 | ul 43 | li do 44 | li 45 | end 46 | end 47 | elements = html.get_elements_by_tag_name("li") 48 | expect(elements.size).to eq(2) 49 | expect(elements[0]).to be_instance_of(Arbre::HTML::Li) 50 | expect(elements[1]).to be_instance_of(Arbre::HTML::Li) 51 | expect(elements[1].parent).to eq(elements[0]) 52 | end 53 | end 54 | 55 | #TODO: describe "finding an element by id" 56 | 57 | describe "finding an element by a class name" do 58 | 59 | it "returns 0 when no elements exist" do 60 | expect(arbre { 61 | div 62 | }.get_elements_by_class_name("my_class").size).to eq(0) 63 | end 64 | 65 | it "allows text nodes on tree" do 66 | expect(arbre { 67 | text_node "text" 68 | }.get_elements_by_class_name("my_class").size).to eq(0) 69 | end 70 | 71 | it "returns a child element" do 72 | html = arbre do 73 | div class: "some_class" 74 | div class: "my_class" 75 | end 76 | elements = html.get_elements_by_class_name("my_class") 77 | expect(elements.size).to eq(1) 78 | expect(elements[0]).to be_instance_of(Arbre::HTML::Div) 79 | end 80 | 81 | it "returns multiple child elements" do 82 | html = arbre do 83 | div class: "some_class" 84 | div class: "my_class" 85 | div class: "my_class" 86 | end 87 | elements = html.get_elements_by_class_name("my_class") 88 | expect(elements.size).to eq(2) 89 | expect(elements[0]).to be_instance_of(Arbre::HTML::Div) 90 | expect(elements[1]).to be_instance_of(Arbre::HTML::Div) 91 | end 92 | 93 | it "returns elements that match one of several classes" do 94 | html = arbre do 95 | div class: "some_class this_class" 96 | div class: "some_class" 97 | div class: "other_class" 98 | 99 | end 100 | elements = html.get_elements_by_class_name("this_class") 101 | expect(elements.size).to eq(1) 102 | expect(elements[0]).to be_instance_of(Arbre::HTML::Div) 103 | end 104 | 105 | it "returns a grandchild element" do 106 | html = arbre do 107 | div class: "some_class" do 108 | div class: "my_class" 109 | end 110 | end 111 | elements = html.get_elements_by_class_name("my_class") 112 | expect(elements.size).to eq(1) 113 | expect(elements[0]).to be_instance_of(Arbre::HTML::Div) 114 | end 115 | 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /spec/arbre/unit/element_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::Element do 5 | 6 | let(:element){ described_class.new } 7 | 8 | context "when initialized" do 9 | 10 | it "has no children" do 11 | expect(element.children).to be_empty 12 | end 13 | 14 | it "has no parent" do 15 | expect(element.parent).to be_nil 16 | end 17 | 18 | it "responds to the HTML builder methods" do 19 | expect(element).to respond_to(:span) 20 | end 21 | 22 | it "has a set of local assigns" do 23 | context = Arbre::Context.new hello: "World" 24 | element = described_class.new(context) 25 | expect(element.assigns[:hello]).to eq("World") 26 | end 27 | 28 | it "has an empty hash with no local assigns" do 29 | expect(element.assigns).to eq({}) 30 | end 31 | 32 | end 33 | 34 | describe "passing in a helper object" do 35 | 36 | let(:helper) do 37 | Class.new do 38 | def helper_method 39 | "helper method" 40 | end 41 | end 42 | end 43 | 44 | let(:element){ described_class.new(Arbre::Context.new(nil, helper.new)) } 45 | 46 | it "calls methods on the helper object and return TextNode objects" do 47 | expect(element.helper_method).to eq("helper method") 48 | end 49 | 50 | it "raises a NoMethodError if not found" do 51 | expect { 52 | element.a_method_that_doesnt_exist 53 | }.to raise_error(NoMethodError) 54 | end 55 | 56 | end 57 | 58 | describe "passing in assigns" do 59 | let(:post) { double } 60 | let(:assigns){ {post: post} } 61 | 62 | it "is accessible via a method call" do 63 | element = described_class.new(Arbre::Context.new(assigns)) 64 | expect(element.post).to eq(post) 65 | end 66 | 67 | end 68 | 69 | it "to_a.flatten should not infinitely recurse" do 70 | expect { 71 | Timeout.timeout(1) do 72 | element.to_a.flatten 73 | end 74 | }.not_to raise_error 75 | end 76 | 77 | describe "adding a child" do 78 | 79 | let(:child){ described_class.new } 80 | 81 | before do 82 | element.add_child child 83 | end 84 | 85 | it "adds the child to the parent" do 86 | expect(element.children.first).to eq(child) 87 | end 88 | 89 | it "sets the parent of the child" do 90 | expect(child.parent).to eq(element) 91 | end 92 | 93 | context "when the child is nil" do 94 | 95 | let(:child){ nil } 96 | 97 | it "does not add the child" do 98 | expect(element.children).to be_empty 99 | end 100 | 101 | end 102 | 103 | context "when the child is a string" do 104 | 105 | let(:child){ "Hello World" } 106 | 107 | it "adds as a TextNode" do 108 | expect(element.children.first).to be_instance_of(Arbre::HTML::TextNode) 109 | expect(element.children.first.to_s).to eq("Hello World") 110 | end 111 | 112 | end 113 | end 114 | 115 | describe "setting the content" do 116 | 117 | context "when a string" do 118 | 119 | before do 120 | element.add_child "Hello World" 121 | element.content = "Goodbye" 122 | end 123 | 124 | it "clears the existing children" do 125 | expect(element.children.size).to eq(1) 126 | end 127 | 128 | it "adds the string as a child" do 129 | expect(element.children.first.to_s).to eq("Goodbye") 130 | end 131 | 132 | it "htmls escape the string" do 133 | string = "Goodbye
    " 134 | element.content = string 135 | expect(element.content.to_s).to eq("Goodbye <br />") 136 | end 137 | end 138 | 139 | context "when an element" do 140 | let(:content_element){ described_class.new } 141 | 142 | before do 143 | element.content = content_element 144 | end 145 | 146 | it "sets the content tag" do 147 | expect(element.children.first).to eq(content_element) 148 | end 149 | 150 | it "sets the tags parent" do 151 | expect(content_element.parent).to eq(element) 152 | end 153 | end 154 | 155 | context "when an array of tags" do 156 | let(:first){ described_class.new } 157 | let(:second){ described_class.new } 158 | 159 | before do 160 | element.content = [first, second] 161 | end 162 | 163 | it "sets the content tag" do 164 | expect(element.children.first).to eq(first) 165 | end 166 | 167 | it "sets the tags parent" do 168 | expect(element.children.first.parent).to eq(element) 169 | end 170 | end 171 | 172 | end 173 | 174 | describe "rendering to html" do 175 | 176 | before { @separator = $, } 177 | after { $, = @separator } # rubocop:disable RSpec/InstanceVariable 178 | 179 | let(:collection){ element + "hello world" } 180 | 181 | it "renders the children collection" do 182 | expect(element.children).to receive(:to_s).and_return("content") 183 | expect(element.to_s).to eq("content") 184 | end 185 | 186 | it "renders collection when is set the default separator" do 187 | suppressing_27_warning { $, = "_" } 188 | 189 | expect(collection.to_s).to eq("hello world") 190 | end 191 | 192 | it "renders collection when is not set the default separator" do 193 | expect(collection.to_s).to eq("hello world") 194 | end 195 | 196 | private 197 | 198 | def suppressing_27_warning 199 | return yield unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.a") 200 | 201 | begin 202 | old_verbose = $VERBOSE 203 | $VERBOSE = nil 204 | yield 205 | ensure 206 | $VERBOSE = old_verbose 207 | end 208 | end 209 | end 210 | 211 | describe "adding elements together" do 212 | 213 | context "when both elements are tags" do 214 | let(:first){ described_class.new } 215 | let(:second){ described_class.new } 216 | let(:collection){ first + second } 217 | 218 | it "returns an instance of Collection" do 219 | expect(collection).to be_an_instance_of(Arbre::ElementCollection) 220 | end 221 | 222 | it "returns the elements in the collection" do 223 | expect(collection.size).to eq(2) 224 | expect(collection.first).to eq(first) 225 | expect(collection[1]).to eq(second) 226 | end 227 | end 228 | 229 | context "when the left is a collection and the right is a tag" do 230 | let(:first){ described_class.new } 231 | let(:second){ described_class.new } 232 | let(:third){ described_class.new } 233 | let(:collection){ Arbre::ElementCollection.new([first, second]) + third } 234 | 235 | it "returns an instance of Collection" do 236 | expect(collection).to be_an_instance_of(Arbre::ElementCollection) 237 | end 238 | 239 | it "returns the elements in the collection flattened" do 240 | expect(collection.size).to eq(3) 241 | expect(collection[0]).to eq(first) 242 | expect(collection[1]).to eq(second) 243 | expect(collection[2]).to eq(third) 244 | end 245 | end 246 | 247 | context "when the right is a collection and the left is a tag" do 248 | let(:first){ described_class.new } 249 | let(:second){ described_class.new } 250 | let(:third){ described_class.new } 251 | let(:collection){ first + Arbre::ElementCollection.new([second,third]) } 252 | 253 | it "returns an instance of Collection" do 254 | expect(collection).to be_an_instance_of(Arbre::ElementCollection) 255 | end 256 | 257 | it "returns the elements in the collection flattened" do 258 | expect(collection.size).to eq(3) 259 | expect(collection[0]).to eq(first) 260 | expect(collection[1]).to eq(second) 261 | expect(collection[2]).to eq(third) 262 | end 263 | end 264 | 265 | context "when the left is a tag and the right is a string" do 266 | let(:element){ described_class.new } 267 | let(:collection){ element + "Hello World"} 268 | 269 | it "returns an instance of Collection" do 270 | expect(collection).to be_an_instance_of(Arbre::ElementCollection) 271 | end 272 | 273 | it "returns the elements in the collection" do 274 | expect(collection.size).to eq(2) 275 | expect(collection[0]).to eq(element) 276 | expect(collection[1]).to be_an_instance_of(Arbre::HTML::TextNode) 277 | end 278 | end 279 | 280 | context "when the left is a string and the right is a tag" do 281 | let(:collection){ "hello World" + described_class.new} 282 | 283 | it "returns a string" do 284 | expect(collection.strip.chomp).to eq("hello World") 285 | end 286 | end 287 | end 288 | 289 | end 290 | -------------------------------------------------------------------------------- /spec/arbre/unit/html/class_list_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::HTML::ClassList do 5 | 6 | describe ".build_from_string" do 7 | 8 | it "builds a new list from a string of classes" do 9 | list = described_class.build_from_string("first second") 10 | expect(list.size).to eq(2) 11 | 12 | expect(list).to match_array(%w{first second}) 13 | end 14 | 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /spec/arbre/unit/html/document_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::HTML::Document do 5 | let(:document){ described_class.new } 6 | 7 | describe "#to_s" do 8 | subject { document.to_s } 9 | 10 | before do 11 | document.build 12 | end 13 | 14 | it { is_expected.to eq "\n" } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/arbre/unit/html/tag_attributes_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::HTML::Tag, "Attributes" do 5 | 6 | let(:tag){ described_class.new } 7 | 8 | describe "attributes" do 9 | 10 | before { tag.build id: "my_id" } 11 | 12 | it "has an attributes hash" do 13 | expect(tag.attributes).to eq({id: "my_id"}) 14 | end 15 | 16 | describe "#to_s" do 17 | it "renders the attributes to html" do 18 | expect(tag.to_s).to eq "\n" 19 | end 20 | 21 | it "renders attributes that are empty but not nil" do 22 | tag.class_list # initializes an empty ClassList 23 | tag.set_attribute :foo, '' 24 | tag.set_attribute :bar, nil 25 | 26 | expect(tag.to_s).to eq "\n" 27 | end 28 | 29 | context "with hyphenated attributes" do 30 | before { tag.build id: "my_id", "data-method" => "get", "data-remote" => true } 31 | 32 | it "renders the attributes to html" do 33 | expect(tag.to_s).to eq "\n" 34 | end 35 | end 36 | 37 | context "when there is a nested attribute" do 38 | before { tag.build id: "my_id", data: { action: 'some_action' } } 39 | 40 | it "flattens the attributes when rendering to html" do 41 | expect(tag.to_s).to eq "\n" 42 | end 43 | 44 | it "renders attributes that are empty but not nil" do 45 | tag.class_list # initializes an empty ClassList 46 | tag.set_attribute :foo, { bar: '' } 47 | tag.set_attribute :bar, { baz: nil } 48 | 49 | expect(tag.to_s).to eq "\n" 50 | end 51 | end 52 | 53 | context "when there is a deeply nested attribute" do 54 | before { tag.build id: "my_id", foo: { bar: { bat: nil, baz: 'foozle' } } } 55 | 56 | it "flattens the attributes when rendering to html" do 57 | expect(tag.to_s).to eq "\n" 58 | end 59 | end 60 | 61 | context "when there are multiple nested attributes" do 62 | before { tag.build id: "my_id", foo: { bar: 'foozle1', bat: nil, baz: '' } } 63 | 64 | it "flattens the attributes when rendering to html" do 65 | expect(tag.to_s).to eq "\n" 66 | end 67 | end 68 | end 69 | 70 | it "gets an attribute value" do 71 | expect(tag.attr(:id)).to eq("my_id") 72 | end 73 | 74 | describe "#has_attribute?" do 75 | context "when the attribute exists" do 76 | it "returns true" do 77 | expect(tag.has_attribute?(:id)).to be(true) 78 | end 79 | end 80 | 81 | context "when the attribute does not exist" do 82 | it "returns false" do 83 | expect(tag.has_attribute?(:class)).to be(false) 84 | end 85 | end 86 | end 87 | 88 | it "removes an attribute" do 89 | expect(tag.attributes).to eq({id: "my_id"}) 90 | expect(tag.remove_attribute(:id)).to eq("my_id") 91 | expect(tag.attributes).to eq({}) 92 | end 93 | end 94 | 95 | describe "rendering attributes" do 96 | it "escapes attribute values" do 97 | tag.set_attribute(:class, '">bad things!') 98 | expect(tag.to_s).to eq "\n" 99 | end 100 | 101 | it "escapes attribute names" do 102 | tag.set_attribute(">bad", "things") 103 | expect(tag.to_s).to eq "\n" 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/arbre/unit/html/tag_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::HTML::Tag do 5 | 6 | let(:tag){ described_class.new } 7 | 8 | describe "building a new tag" do 9 | before { tag.build "Hello World", id: "my_id" } 10 | 11 | it "sets the contents to a string" do 12 | expect(tag.content).to eq("Hello World") 13 | end 14 | 15 | it "sets the hash of options to the attributes" do 16 | expect(tag.attributes).to eq({ id: "my_id" }) 17 | end 18 | end 19 | 20 | describe "creating a tag 'for' an object" do 21 | # rubocop:disable RSpec/VerifiedDoubles 22 | let(:model_name){ double(singular: "resource_class")} 23 | let(:resource_class){ double(model_name: model_name) } 24 | let(:resource){ double(class: resource_class, to_key: ['5'])} 25 | # rubocop:enable RSpec/VerifiedDoubles 26 | 27 | before do 28 | tag.build for: resource 29 | end 30 | 31 | it "sets the id to the type and id" do 32 | expect(tag.id).to eq("resource_class_5") 33 | end 34 | 35 | it "adds a class name" do 36 | expect(tag.class_list).to include("resource_class") 37 | end 38 | 39 | describe "for an object that doesn't have a model_name" do 40 | let(:resource_class){ double(name: 'ResourceClass') } # rubocop:disable RSpec/VerifiedDoubles 41 | 42 | before do 43 | tag.build for: resource 44 | end 45 | 46 | it "sets the id to the type and id" do 47 | expect(tag.id).to eq("resource_class_5") 48 | end 49 | 50 | it "adds a class name" do 51 | expect(tag.class_list).to include("resource_class") 52 | end 53 | end 54 | 55 | describe "with a default_id_for_prefix" do 56 | 57 | let(:tag) do 58 | Class.new(Arbre::HTML::Tag) do 59 | def default_id_for_prefix 60 | "a_prefix" 61 | end 62 | end.new 63 | end 64 | 65 | it "sets the id to the type and id" do 66 | expect(tag.id).to eq("a_prefix_resource_class_5") 67 | end 68 | 69 | end 70 | end 71 | 72 | describe "creating a tag with a for attribute" do 73 | it "sets the `for` attribute when a string is given" do 74 | tag.build for: "email" 75 | expect(tag.attributes[:for]).to eq "email" 76 | end 77 | 78 | it "sets the `for` attribute when a symbol is given" do 79 | tag.build for: :email 80 | expect(tag.attributes[:for]).to eq :email 81 | end 82 | end 83 | 84 | describe "css class names" do 85 | 86 | it "adds a class" do 87 | tag.add_class "hello_world" 88 | expect(tag.class_names).to eq("hello_world") 89 | end 90 | 91 | it "removes classes" do 92 | tag.add_class "hello_world" 93 | expect(tag.class_names).to eq("hello_world") 94 | tag.remove_class "hello_world" 95 | expect(tag.class_names).to eq("") 96 | end 97 | 98 | it "does not add a class if it already exists" do 99 | tag.add_class "hello_world" 100 | tag.add_class "hello_world" 101 | expect(tag.class_names).to eq("hello_world") 102 | end 103 | 104 | it "separates classes with space" do 105 | tag.add_class "hello world" 106 | expect(tag.class_list.size).to eq(2) 107 | end 108 | 109 | it "creates a class list from a string" do 110 | tag = described_class.new 111 | tag.build(class: "first-class") 112 | tag.add_class "second-class" 113 | expect(tag.class_list.size).to eq(2) 114 | end 115 | 116 | end 117 | 118 | end 119 | -------------------------------------------------------------------------------- /spec/arbre/unit/html/text_node_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | describe Arbre::HTML::TextNode do 5 | let(:text_node){ described_class.new } 6 | 7 | describe '#class_list' do 8 | subject { text_node.class_list } 9 | 10 | it { is_expected.to be_empty } 11 | end 12 | 13 | describe '#tag_name' do 14 | subject { text_node.tag_name } 15 | 16 | it { is_expected.to be_nil } 17 | end 18 | 19 | describe '#to_s' do 20 | subject { text_node.build('Test').to_s } 21 | 22 | it { is_expected.to eq 'Test' } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/changelog_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | RSpec.describe "Changelog" do 5 | subject(:changelog) do 6 | path = File.join(File.dirname(__dir__), "CHANGELOG.md") 7 | File.read(path) 8 | end 9 | 10 | it 'has definitions for all implicit links' do 11 | implicit_link_names = changelog.scan(/\[([^\]]+)\]\[\]/).flatten.uniq 12 | implicit_link_names.each do |name| 13 | expect(changelog).to include("[#{name}]: https") 14 | end 15 | end 16 | 17 | describe 'entry' do 18 | subject(:entries) { lines.grep(/^\*/) } 19 | 20 | let(:lines) { changelog.each_line } 21 | 22 | it 'does not end with a punctuation' do 23 | entries.each do |entry| 24 | expect(entry).not_to match(/\.$/) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/gemspec_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "spec_helper" 3 | require "open3" 4 | require "arbre/version" 5 | 6 | RSpec.describe "Gemspec" do 7 | after do 8 | File.delete("arbre-#{Arbre::VERSION}.gem") 9 | end 10 | 11 | let(:build) do 12 | Bundler.with_original_env do 13 | Open3.capture3("gem build arbre") 14 | end 15 | end 16 | 17 | it "succeeds" do 18 | expect(build[2]).to be_success 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/rails/integration/forms_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'rails/rails_spec_helper' 3 | 4 | RSpec.describe "Forms" do 5 | 6 | let(:assigns){ {} } 7 | let(:helpers){ mock_action_view } 8 | let(:html) { form.to_s } 9 | 10 | describe "building a simple form for" do 11 | 12 | let(:form) do 13 | arbre do 14 | form_for MockPerson.new, url: "/" do |f| 15 | f.label :name 16 | f.text_field :name 17 | end 18 | end 19 | end 20 | 21 | it "builds a form" do 22 | expect(html).to have_css("form") 23 | end 24 | 25 | it "includes the hidden authenticity token" do 26 | expect(html).to have_field("authenticity_token", type: :hidden, with: "AUTH_TOKEN") 27 | end 28 | 29 | it "creates a label" do 30 | expect(html).to have_css("form label[for=mock_person_name]") 31 | end 32 | 33 | it "creates a text field" do 34 | expect(html).to have_css("form input[type=text]") 35 | end 36 | 37 | end 38 | 39 | describe "building a form with fields for" do 40 | 41 | let(:form) do 42 | arbre do 43 | form_for MockPerson.new, url: "/" do |f| 44 | f.label :name 45 | f.text_field :name 46 | f.fields_for :permission do |pf| 47 | pf.label :admin 48 | pf.check_box :admin 49 | end 50 | end 51 | end 52 | end 53 | 54 | it "renders nested label" do 55 | expect(html).to have_css("form label[for=mock_person_permission_admin]", text: "Admin") 56 | end 57 | 58 | it "renders nested input" do 59 | expect(html).to have_css("form input[type=checkbox][name='mock_person[permission][admin]']") 60 | end 61 | 62 | it "does not render a div for the proxy" do 63 | expect(html).to have_no_css("form div.fields_for_proxy") 64 | end 65 | 66 | end 67 | 68 | describe "forms with other elements" do 69 | let(:form) do 70 | arbre do 71 | form_for MockPerson.new, url: "/" do |f| 72 | 73 | div do 74 | f.label :name 75 | f.text_field :name 76 | end 77 | 78 | para do 79 | f.label :name 80 | f.text_field :name 81 | end 82 | 83 | div class: "permissions" do 84 | f.fields_for :permission do |pf| 85 | div class: "permissions_label" do 86 | pf.label :admin 87 | end 88 | pf.check_box :admin 89 | end 90 | end 91 | 92 | end 93 | end 94 | end 95 | 96 | it "nests elements" do 97 | expect(html).to have_css("form > p > label") 98 | end 99 | 100 | it "nests elements within fields for" do 101 | expect(html).to have_css("form > div.permissions > div.permissions_label label") 102 | end 103 | end 104 | 105 | end 106 | -------------------------------------------------------------------------------- /spec/rails/integration/rendering_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'rails/rails_spec_helper' 3 | 4 | ARBRE_VIEWS_PATH = File.expand_path("../../templates", __FILE__) 5 | 6 | class TestController < ActionController::Base 7 | append_view_path ARBRE_VIEWS_PATH 8 | 9 | def render_empty 10 | render "arbre/empty" 11 | end 12 | 13 | def render_simple_page 14 | render "arbre/simple_page" 15 | end 16 | 17 | def render_partial 18 | render "arbre/page_with_partial" 19 | end 20 | 21 | def render_erb_partial 22 | render "arbre/page_with_erb_partial" 23 | end 24 | 25 | def render_with_instance_variable 26 | @my_instance_var = "From Instance Var" 27 | render "arbre/page_with_assignment" 28 | end 29 | 30 | def render_partial_with_instance_variable 31 | @my_instance_var = "From Instance Var" 32 | render "arbre/page_with_arb_partial_and_assignment" 33 | end 34 | 35 | def render_with_block 36 | render "arbre/page_with_render_with_block" 37 | end 38 | end 39 | 40 | RSpec.describe TestController, "Rendering with Arbre", type: :request do 41 | let(:body){ response.body } 42 | 43 | before do 44 | Rails.application.routes.draw do 45 | get 'test/render_empty', controller: "test" 46 | get 'test/render_simple_page', controller: "test" 47 | get 'test/render_partial', controller: "test" 48 | get 'test/render_erb_partial', controller: "test" 49 | get 'test/render_with_instance_variable', controller: "test" 50 | get 'test/render_partial_with_instance_variable', controller: "test" 51 | get 'test/render_page_with_helpers', controller: "test" 52 | get 'test/render_with_block', controller: "test" 53 | end 54 | end 55 | 56 | after do 57 | Rails.application.reload_routes! 58 | end 59 | 60 | it "renders the empty template" do 61 | get "/test/render_empty" 62 | expect(response).to be_successful 63 | end 64 | 65 | it "renders a simple page" do 66 | get "/test/render_simple_page" 67 | expect(response).to be_successful 68 | expect(body).to have_css("h1", text: "Hello World") 69 | expect(body).to have_css("p", text: "Hello again!") 70 | end 71 | 72 | it "renders an arb partial" do 73 | get "/test/render_partial" 74 | expect(response).to be_successful 75 | expect(body).to eq <<~HTML 76 |

    Before Partial

    77 |

    Hello from a partial

    78 |

    After Partial

    79 | HTML 80 | end 81 | 82 | it "renders an erb (or other) partial" do 83 | get "/test/render_erb_partial" 84 | expect(response).to be_successful 85 | expect(body).to eq <<~HTML 86 |

    Before Partial

    87 |

    Hello from an erb partial

    88 |

    After Partial

    89 | HTML 90 | end 91 | 92 | it "renders with instance variables" do 93 | get "/test/render_with_instance_variable" 94 | expect(response).to be_successful 95 | expect(body).to have_css("h1", text: "From Instance Var") 96 | end 97 | 98 | it "renders an arbre partial with assignments" do 99 | get "/test/render_partial_with_instance_variable" 100 | expect(response).to be_successful 101 | expect(body).to have_css("p", text: "Partial: From Instance Var") 102 | end 103 | 104 | it "renders with a block" do 105 | get "/test/render_with_block" 106 | expect(response).to be_successful 107 | expect(body).to eq <<~HTML 108 |

    Before Render

    109 | Hello from a render block 110 |

    After Render

    111 | HTML 112 | end 113 | 114 | end 115 | -------------------------------------------------------------------------------- /spec/rails/rails_spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'spec_helper' 3 | 4 | Combustion.path = 'spec/rails/stub_app' 5 | Combustion.initialize! :action_controller, :action_view do 6 | if Rails.gem_version >= Gem::Version.new("7.1.0") 7 | config.active_support.cache_format_version = 7.1 8 | end 9 | end 10 | 11 | require 'rspec/rails' 12 | require 'capybara/rspec' 13 | require 'capybara/rails' 14 | 15 | require 'rails/support/mock_person' 16 | 17 | module AdditionalHelpers 18 | 19 | def protect_against_forgery? 20 | true 21 | end 22 | 23 | def form_authenticity_token(form_options: {}) 24 | "AUTH_TOKEN" 25 | end 26 | 27 | end 28 | 29 | def mock_action_view(assigns = {}) 30 | controller = ActionView::TestCase::TestController.new 31 | ActionView::Base.include(ActionView::Helpers) 32 | ActionView::Base.include(AdditionalHelpers) 33 | context = ActionView::LookupContext.new(ActionController::Base.view_paths) 34 | ActionView::Base.new(context, assigns, controller) 35 | end 36 | 37 | RSpec.configure do |config| 38 | config.include Capybara::RSpecMatchers 39 | config.mock_with :rspec do |mocks| 40 | mocks.verify_partial_doubles = true 41 | end 42 | config.disable_monkey_patching! 43 | config.order = :random 44 | Kernel.srand config.seed 45 | end 46 | -------------------------------------------------------------------------------- /spec/rails/stub_app/config/database.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: sqlite3 3 | database: db/combustion_test.sqlite 4 | -------------------------------------------------------------------------------- /spec/rails/stub_app/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Rails.application.routes.draw do 3 | # 4 | end 5 | -------------------------------------------------------------------------------- /spec/rails/stub_app/db/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # ActiveRecord::Schema.define do 3 | # # 4 | # end 5 | -------------------------------------------------------------------------------- /spec/rails/stub_app/log/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /spec/rails/stub_app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activeadmin/arbre/37115346dc49e88292ecec8fb5d2d14a774ade1b/spec/rails/stub_app/public/favicon.ico -------------------------------------------------------------------------------- /spec/rails/support/mock_person.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'active_model' 3 | 4 | class MockPerson 5 | extend ActiveModel::Naming 6 | 7 | attr_accessor :name 8 | 9 | def persisted? 10 | false 11 | end 12 | 13 | def to_key 14 | [] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/_partial.arb: -------------------------------------------------------------------------------- 1 | para "Hello from a partial" 2 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/_partial_with_assignment.arb: -------------------------------------------------------------------------------- 1 | para "Partial: #{my_instance_var}" 2 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/empty.arb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activeadmin/arbre/37115346dc49e88292ecec8fb5d2d14a774ade1b/spec/rails/templates/arbre/empty.arb -------------------------------------------------------------------------------- /spec/rails/templates/arbre/page_with_arb_partial_and_assignment.arb: -------------------------------------------------------------------------------- 1 | h1 "Before Partial" 2 | render "arbre/partial_with_assignment" 3 | h2 "After Partial" 4 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/page_with_assignment.arb: -------------------------------------------------------------------------------- 1 | h1 my_instance_var 2 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/page_with_erb_partial.arb: -------------------------------------------------------------------------------- 1 | h1 "Before Partial" 2 | render "erb/partial" 3 | h2 "After Partial" 4 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/page_with_partial.arb: -------------------------------------------------------------------------------- 1 | h1 "Before Partial" 2 | render "arbre/partial" 3 | h2 "After Partial" 4 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/page_with_render_with_block.arb: -------------------------------------------------------------------------------- 1 | render_in_object = Class.new do 2 | def render_in(_, &block) 3 | block.call 4 | end 5 | end.new 6 | 7 | h1 "Before Render" 8 | render render_in_object do 9 | "Hello from a render block\n" 10 | end 11 | h2 "After Render" 12 | -------------------------------------------------------------------------------- /spec/rails/templates/arbre/simple_page.arb: -------------------------------------------------------------------------------- 1 | html do 2 | head do 3 | end 4 | body do 5 | h1 "Hello World" 6 | para "Hello again!" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/rails/templates/erb/_partial.erb: -------------------------------------------------------------------------------- 1 |

    Hello from an erb partial

    2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if ENV.fetch('COVERAGE', false) 4 | require 'simplecov' 5 | require 'simplecov-cobertura' 6 | SimpleCov.start do 7 | add_filter %r{^/spec/} 8 | minimum_coverage 90 9 | maximum_coverage_drop 0.2 10 | formatter SimpleCov::Formatter::CoberturaFormatter 11 | end 12 | end 13 | 14 | require 'support/bundle' 15 | require 'combustion' 16 | require 'arbre' 17 | 18 | def arbre(&block) 19 | Arbre::Context.new assigns, helpers, &block 20 | end 21 | -------------------------------------------------------------------------------- /spec/support/bundle.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | $LOAD_PATH.unshift(File.expand_path("../../", __FILE__)) 3 | 4 | require "bundler" 5 | Bundler.setup 6 | --------------------------------------------------------------------------------