Skip to content

fix: making pruneSource work#138

Merged
danielroe merged 6 commits into
danielroe:mainfrom
shishkin17:main
Jul 8, 2025
Merged

fix: making pruneSource work#138
danielroe merged 6 commits into
danielroe:mainfrom
shishkin17:main

Conversation

@shishkin17
Copy link
Copy Markdown
Contributor

@shishkin17 shishkin17 commented Apr 28, 2025

It turns out they already calculate pruned styles, they just don't write them to the source css file.

Description by Callstackai

This PR fixes the functionality of pruneSource by ensuring that pruned styles are written to the source CSS file.

Diagrams of code changes
sequenceDiagram
    participant Client
    participant Beasties
    participant FileSystem

    Client->>Beasties: process(html)
    Note over Beasties: Process HTML and extract critical CSS
    
    alt pruneSource enabled
        Beasties->>FileSystem: writeFile(cssFilePath, nonCriticalCSS)
        alt Success
            FileSystem-->>Beasties: resolve()
            Beasties->>Beasties: logger.info("file updated")
        else Error
            FileSystem-->>Beasties: reject(error)
            Beasties->>Beasties: logger.error(error)
        end
    end
    
    Beasties-->>Client: return processed HTML
Loading
Files Changed
FileSummary
packages/beasties/src/index.tsAdded a method to write content to a file and updated the process to write pruned styles.
packages/beasties/test/__snapshots__/beasties.test.ts.snapUpdated snapshot to reflect changes in the HTML structure when pruneSource is enabled.
packages/beasties/test/beasties.test.tsAdded a test case to verify that the CSS file is updated when pruneSource is enabled.
packages/beasties/test/src/prune-source.cssCreated a new CSS file with styles for testing the pruneSource feature.
packages/beasties/test/src/prune-source.htmlCreated a new HTML file for testing the pruneSource feature.

This PR includes files in programming languages that we currently do not support. We have not reviewed files with the extensions .snap, .css, .html. See list of supported languages.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2025

## Walkthrough

The update introduces logic to write the pruned (inverse) CSS stylesheet to disk within the `processStyle` method of the `Beasties` class. After pruning and serialising the CSS, if pruning is enabled, the code generates a file path using the configured output directory and stylesheet name, then writes the pruned CSS content to this file asynchronously. It also logs either an error or a success message based on the outcome of the file write operation. Additionally, a new asynchronous `writeFile` method was added to the `Beasties` class to handle file writing using a provided filesystem interface or the native Node.js method. No changes were made to exported or public entity signatures besides the addition of this method.

## Changes

| File(s)                                         | Change Summary                                                                                                  |
|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| packages/beasties/src/index.ts                   | Added asynchronous `writeFile` method; enhanced `processStyle` to write pruned CSS to disk asynchronously with logging after pruning. |
| packages/beasties/test/beasties.test.ts          | Added new test verifying CSS file update when `pruneSource` is enabled, including assertions on pruned CSS content and logging. |
| packages/beasties/test/src/prune-source.css      | Added new CSS file containing various style rules used for pruning tests.                                       |
| packages/beasties/test/src/prune-source.html     | Added new HTML test fixture linking to the new CSS file, used for testing pruning functionality.                |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Beasties
    participant FileSystem

    User->>Beasties: processStyle()
    alt Pruning enabled
        Beasties->>Beasties: Prune and serialise CSS
        Beasties->>Beasties: Resolve full stylesheet path
        Beasties->>FileSystem: writeFile(pruned CSS)
        FileSystem-->>Beasties: Success/Error callback
        Beasties->>Beasties: Log success or error
    else Pruning not enabled
        Beasties->>Beasties: Skip writing pruned CSS
    end
    Beasties-->>User: Complete processStyle

Suggested reviewers

  • danielroe

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details open="true">
<summary>🧪 Generate Unit Tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with Unit Tests
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post Copyable Unit Tests in a Comment

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=danielroe/beasties&utm_content=138):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai auto-generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link
Copy Markdown

@callstackai callstackai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key Issues

The code is vulnerable to directory traversal attacks due to unsanitized path joining, allowing potential unauthorized writes outside the intended directory.

Comment thread packages/beasties/src/index.ts Outdated
Comment thread packages/beasties/src/index.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/beasties/src/index.ts (2)

673-676: ⚠️ Potential issue

Security vulnerability: potential path traversal attack

The code directly joins this.options.path with name without sanitizing or validating the paths. Since name comes from style.$$name, which could contain '../' sequences, this could lead to directory traversal vulnerabilities allowing writes outside the intended directory.

Apply this change to fix the security vulnerability:

-      const cssFilePath = path.resolve(this.options.path, name)
+      const cssFilePath = path.resolve(this.options.path, path.basename(name))

673-676: 🛠️ Refactor suggestion

Consider handling file writing through a plugin system

The current implementation writes directly to the filesystem, which might not be appropriate for all use cases, particularly for webpack/vite plugins that work with virtual or generated files.

Based on the previous comments by danielroe and alan-agius4, consider implementing a plugin system for file writing:

-      const cssFilePath = path.resolve(this.options.path, path.basename(name))
-      this.writeFile(cssFilePath, sheetInverse)
-        .then(() => this.logger.info?.(`${name} was successfully updated`))
-        .catch(err => this.logger.error?.(err))
+      // Allow plugins to intercept file writing
+      const cssFilePath = path.resolve(this.options.path, path.basename(name))
+      const writePromise = typeof this.options.onWritePrunedCss === 'function'
+        ? this.options.onWritePrunedCss(cssFilePath, sheetInverse)
+        : this.writeFile(cssFilePath, sheetInverse)
+      
+      writePromise
+        .then(() => this.logger.info?.(`${name} was successfully updated`))
+        .catch(err => this.logger.error?.(err))

This would require adding a new option to the Options interface:

interface Options {
  // ...
  onWritePrunedCss?: (path: string, content: string) => Promise<void>;
  // ...
}
🧹 Nitpick comments (1)
packages/beasties/src/index.ts (1)

75-94: New writeFile method looks good but consider using optional chaining

The implementation mirrors the existing readFile method's pattern and correctly handles both custom filesystem interfaces and Node.js native methods.

Consider using optional chaining on line 87 as suggested by the static analysis tool:

-      if (fs && fs.writeFile) {
+      if (fs?.writeFile) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 87-87: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 837df56 and 931346b.

📒 Files selected for processing (1)
  • packages/beasties/src/index.ts (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/beasties/src/index.ts

[error] 87-87: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (1)
packages/beasties/src/index.ts (1)

22-22: Import statement added correctly

The addition of writeFile import to accompany the existing readFile import is necessary for the new functionality.

Copy link
Copy Markdown

@callstackai callstackai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key Issues

The code risks runtime errors by assuming a global writeFile function exists when this.fs is undefined, and it should be imported from 'node:fs' like readFile.

Comment thread packages/beasties/src/index.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/beasties/src/index.ts (1)

673-676: ⚠️ Potential issue

Potential directory traversal vulnerability.

The code joins this.options.path with name without extracting just the filename part. Since name comes from style.$$name, which could potentially contain '../' sequences, this might allow writing files outside the intended directory.

Apply this fix to mitigate the vulnerability:

-      const cssFilePath = path.resolve(this.options.path, name)
+      const cssFilePath = path.resolve(this.options.path, path.basename(name))

This matches the approach suggested in previous review comments.

🧹 Nitpick comments (1)
packages/beasties/src/index.ts (1)

75-94: Well-implemented writeFile method.

This method mirrors the existing readFile method pattern which is good for consistency, and it correctly supports custom filesystem implementations - important for plugin support as noted in previous reviews.

Consider using optional chaining for a cleaner check:

-      if (fs && fs.writeFile) {
+      if (fs?.writeFile) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 87-87: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 931346b and 5786177.

📒 Files selected for processing (1)
  • packages/beasties/src/index.ts (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/beasties/src/index.ts

[error] 87-87: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (1)
packages/beasties/src/index.ts (1)

674-676: Good error handling with appropriate logging.

The promise chain correctly handles both success and error cases with appropriate logging messages.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/beasties/src/index.ts (1)

75-93: Good implementation of the writeFile method.

The new writeFile method follows the same pattern as the existing readFile method, which is good for consistency. It properly handles both the provided filesystem interface and the native Node.js method.

Consider using optional chaining for a more concise check:

-      if (fs && fs.writeFile) {
+      if (fs?.writeFile) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 86-86: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5786177 and 3c45327.

📒 Files selected for processing (1)
  • packages/beasties/src/index.ts (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/beasties/src/index.ts

[error] 86-86: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (2)
packages/beasties/src/index.ts (2)

22-22: Import looks good.

The addition of the writeFile import from 'node:fs' is consistent with the existing readFile import pattern.


672-675: Implementation successfully writes pruned CSS back to the source file.

The code correctly:

  1. Resolves the full file path using path.resolve
  2. Writes the pruned CSS to the source file asynchronously
  3. Logs appropriate success or error messages

This implementation properly addresses the main objective of the PR to ensure pruned CSS is written back to the source file.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 29, 2025

CodSpeed Performance Report

Merging #138 will not alter performance

Comparing shishkin17:main (bb0efbe) with main (7133ff1)

Summary

✅ 9 untouched benchmarks

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 29, 2025

Codecov Report

❌ Patch coverage is 90.47619% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.14%. Comparing base (7133ff1) to head (bb0efbe).
⚠️ Report is 126 commits behind head on main.

Files with missing lines Patch % Lines
packages/beasties/src/index.ts 90.47% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #138      +/-   ##
==========================================
+ Coverage   85.93%   86.14%   +0.21%     
==========================================
  Files           8        8              
  Lines        1294     1314      +20     
  Branches      311      319       +8     
==========================================
+ Hits         1112     1132      +20     
  Misses        182      182              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@danielroe danielroe requested a review from alan-agius4 April 29, 2025 10:39
@shishkin17
Copy link
Copy Markdown
Contributor Author

When pruneSource started to work, a bug is revealed: @media rules are not always handled properly.

For example if I have a rule:

@media (hover: hover) {
    &:hover {
      color: grey;
    }
  }

sometimes it converts to (in pruned file):

@media (hover: hover) {
  }

Without pruneSource it is not noticeable, because the original file remains untouched.

@shishkin17
Copy link
Copy Markdown
Contributor Author

So what do we do next @danielroe?
This @media issue is not related directly with pruneSource. Maybe we should continue with this PR and create a separate issue for @media case?

Copy link
Copy Markdown
Owner

@danielroe danielroe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you add a test for this? 🙏

(yes, let's address the @media issue in a separate issue/PR)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c45327 and bb0efbe.

⛔ Files ignored due to path filters (1)
  • packages/beasties/test/__snapshots__/beasties.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • packages/beasties/test/beasties.test.ts (1 hunks)
  • packages/beasties/test/src/prune-source.css (1 hunks)
  • packages/beasties/test/src/prune-source.html (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/beasties/test/src/prune-source.css
🧰 Additional context used
🪛 HTMLHint (1.5.0)
packages/beasties/test/src/prune-source.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🔇 Additional comments (3)
packages/beasties/test/src/prune-source.html (1)

1-21: HTML test fixture structure looks appropriate for testing CSS pruning functionality.

The HTML structure effectively provides a variety of elements and classes to test the pruning logic. The missing doctype flagged by HTMLHint is likely intentional for this test fixture, as it focuses on testing the CSS pruning behaviour rather than HTML validation.

packages/beasties/test/beasties.test.ts (2)

349-385: Comprehensive test case for pruneSource functionality.

The test implementation is well-structured and covers the key aspects of the pruneSource feature:

  • Proper setup with logger and pruneSource configuration
  • Appropriate writeFile method override using Promise wrapper
  • Thorough verification of both HTML output and CSS file content
  • Good error handling in the writeFile mock

The test expectations look reasonable and the structure follows the existing patterns in the test suite.


366-374: Well-implemented writeFile override for testing.

The Promise wrapper around fs.writeFileSync is correctly implemented with proper error handling. This approach ensures the test can verify the file writing behaviour whilst maintaining the asynchronous interface expected by the Beasties class.

Comment on lines +378 to +381
expect(result).toContain('<style>h1{color:blue}p{color:purple}.contents{padding:50px;text-align:center}.input-field{padding:10px}div:is(:hover,.active){color:#000}div:is(.selected,:hover){color:#fff}</style>')

const css = fs.readFileSync(path.join(fixtureDir, 'prune-source.css'), 'utf-8')
expect(css).toEqual('h2.unused{color:red}p.unused{color:orange}header{padding:0 50px}.banner{font-family:sans-serif}footer{margin-top:10px}.container{border:1px solid}.custom-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.other-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.custom-element::part(tab):hover{background-color:#0c0d19;color:#ffffff;border-color:#0c0d33}.custom-element::part(tab):hover:active{background-color:#0c0d33;color:#ffffff}.custom-element::part(tab):focus{box-shadow:0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3)}.custom-element::part(active){color:#0060df;border-color:#0a84ff !important}')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the expected CSS content matches the actual pruning logic.

The test expects specific CSS content after pruning. Ensure that the expected content in the assertion accurately reflects what the pruning algorithm should produce for the given HTML fixture.


🏁 Script executed:

#!/bin/bash
# Verify that the expected CSS content matches the actual pruning behaviour
# by examining the corresponding CSS fixture file

# Find and display the CSS fixture file content
fd -e css prune-source.css packages/beasties/test/src/ --exec cat {}

# Also check if there are any related CSS files that might be used
fd -e css . packages/beasties/test/src/ --exec ls -la {}

Length of output: 1233


Update the expected CSS snapshot in the prune‐source assertion

The test’s hard-coded CSS doesn’t match the contents of prune-source.css (which now includes the h1, .contents, .input-field and the div:is() rules). Please update the assertion in packages/beasties/test/beasties.test.ts to reflect the actual fixture content:

• File: packages/beasties/test/beasties.test.ts
• Location: around the expect(css).toEqual(…) on lines 380–381

Proposed diff:

-    expect(css).toEqual(
-      'h2.unused{color:red}p.unused{color:orange}header{padding:0 50px}.banner{font-family:sans-serif}footer{margin-top:10px}.container{border:1px solid}.custom-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.other-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.custom-element::part(tab):hover{background-color:#0c0d19;color:#ffffff;border-color:#0c0d33}.custom-element::part(tab):hover:active{background-color:#0c0d33;color:#ffffff}.custom-element::part(tab):focus{box-shadow:0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3)}.custom-element::part(active){color:#0060df;border-color:#0a84ff !important}'
-    )
+    expect(css).toEqual(
+      'h1{color:blue}h2.unused{color:red}p{color:purple}p.unused{color:orange}header{padding:0 50px}.banner{font-family:sans-serif}.contents{padding:50px;text-align:center}.input-field{padding:10px}footer{margin-top:10px}.container{border:1px solid}.custom-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.other-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.custom-element::part(tab):hover{background-color:#0c0d19;color:#ffffff;border-color:#0c0d33}.custom-element::part(tab):hover:active{background-color:#0c0d33;color:#ffffff}.custom-element::part(tab):focus{box-shadow:0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3)}.custom-element::part(active){color:#0060df;border-color:#0a84ff !important}div:is(:hover,.active){color:#000}div:is(.selected,:hover){color:#fff}'
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(result).toContain('<style>h1{color:blue}p{color:purple}.contents{padding:50px;text-align:center}.input-field{padding:10px}div:is(:hover,.active){color:#000}div:is(.selected,:hover){color:#fff}</style>')
const css = fs.readFileSync(path.join(fixtureDir, 'prune-source.css'), 'utf-8')
expect(css).toEqual('h2.unused{color:red}p.unused{color:orange}header{padding:0 50px}.banner{font-family:sans-serif}footer{margin-top:10px}.container{border:1px solid}.custom-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.other-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.custom-element::part(tab):hover{background-color:#0c0d19;color:#ffffff;border-color:#0c0d33}.custom-element::part(tab):hover:active{background-color:#0c0d33;color:#ffffff}.custom-element::part(tab):focus{box-shadow:0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3)}.custom-element::part(active){color:#0060df;border-color:#0a84ff !important}')
const css = fs.readFileSync(path.join(fixtureDir, 'prune-source.css'), 'utf-8')
expect(css).toEqual(
'h1{color:blue}h2.unused{color:red}p{color:purple}p.unused{color:orange}header{padding:0 50px}.banner{font-family:sans-serif}.contents{padding:50px;text-align:center}.input-field{padding:10px}footer{margin-top:10px}.container{border:1px solid}.custom-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.other-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px}.custom-element::part(tab):hover{background-color:#0c0d19;color:#ffffff;border-color:#0c0d33}.custom-element::part(tab):hover:active{background-color:#0c0d33;color:#ffffff}.custom-element::part(tab):focus{box-shadow:0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3)}.custom-element::part(active){color:#0060df;border-color:#0a84ff !important}div:is(:hover,.active){color:#000}div:is(.selected,:hover){color:#fff}'
)
🤖 Prompt for AI Agents
In packages/beasties/test/beasties.test.ts around lines 380 to 381, the expected
CSS string in the assertion does not match the actual contents of
prune-source.css, which now includes additional rules like h1, .contents,
.input-field, and div:is() selectors. Update the expected string in the
expect(css).toEqual(...) call to exactly match the current contents of
prune-source.css, including all new CSS rules and formatting.

@shishkin17 shishkin17 requested a review from danielroe July 5, 2025 06:19
@shishkin17
Copy link
Copy Markdown
Contributor Author

Hi @danielroe, I finally found time to write the test)

Copy link
Copy Markdown
Owner

@danielroe danielroe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! ❤️

@danielroe danielroe merged commit 914536a into danielroe:main Jul 8, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants