/**
 * Integration tests for the adblock-rs native Node.js addon (js/index.js).
 *
 * These tests document the current behaviour of the Neon binding layer and
 * serve as a regression baseline for the NAPI-RS migration.
 *
 * Run:  node --test js/test/bindings.test.mjs
 *       (requires the native addon to be built first: npm run build)
 */

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const { FilterSet, Engine, FilterFormat, RuleTypes, uBlockResources } =
    createRequire(import.meta.url)(join(__dirname, '..', 'index.js'));

// ---------------------------------------------------------------------------
// FilterSet.addFilters
// ---------------------------------------------------------------------------

describe('FilterSet.addFilters', () => {
      it('parses metadata comments (title, homepage, expires, redirect)', () => {
        const fs = new FilterSet();
        const { source_index, metadata } = fs.addFilters([
            '! Title: Test List',
            '! Homepage: https://example.com',
            '! Expires: 2 days',
            '! Redirect: https://example.com/new-list.txt',
            '||ads.com^',
        ].join('\n'));
        assert.equal(metadata.title, 'Test List');
        assert.equal(source_index, 0);
        assert.equal(metadata.homepage, 'https://example.com');
        assert.ok(metadata.expires != null);
        assert.equal(metadata.redirect, 'https://example.com/new-list.txt');
    });
    it('hosts format parses IP-hostname entries', () => {
        const fs = new FilterSet();
        fs.addFilters('127.0.0.1 ads.example.com', { format: FilterFormat.HOSTS });
        const engine = new Engine(fs);
        assert.equal(
            engine.check('https://ads.example.com/', 'https://pub.com', 'script'),
            true,
        );
    });

    it('NETWORK_ONLY skips cosmetic rules', () => {
        const fs = new FilterSet();
        fs.addFilters(
            ['||example.com^', 'example.com##.ad'].join('\n'),
            { rule_types: RuleTypes.NETWORK_ONLY },
        );
        const engine = new Engine(fs);
        assert.equal(
            engine.check('https://example.com/test.js', 'https://pub.com', 'script'),
            true,
        );
        const cosmetic = engine.urlCosmeticResources('https://example.com/page');
        assert.equal(cosmetic.hide_selectors.length, 0);
    });

    it("Legacy addFilters syntax works", () => {
      const fs = new FilterSet();
      fs.addFilters(["||example.com^", "example.com##.ad"], {
        rule_types: RuleTypes.NETWORK_ONLY,
      });
      const engine = new Engine(fs);
      assert.equal(
        engine.check(
          "https://example.com/test.js",
          "https://pub.com",
          "script",
        ),
        true,
      );
    });

    it('COSMETIC_ONLY skips network rules', () => {
        const fs = new FilterSet();
        fs.addFilters(
            ['||example.com^', 'example.com##.ad'].join('\n'),
            { rule_types: RuleTypes.COSMETIC_ONLY },
        );
        const engine = new Engine(fs);
        assert.equal(
            engine.check('https://example.com/test.js', 'https://pub.com', 'script'),
            false,
        );
        const cosmetic = engine.urlCosmeticResources('https://example.com/page');
        assert.ok(cosmetic.hide_selectors.includes('.ad'));
    });
});

// ---------------------------------------------------------------------------
// FilterSet.intoContentBlocking
// ---------------------------------------------------------------------------

describe('FilterSet.intoContentBlocking', () => {
    it('converts network filters to content blocking rules with trigger/action', () => {
        const fs = new FilterSet(true);
        fs.addFilters('||ads.example.com^');
        const result = fs.intoContentBlocking();
        assert.notEqual(result, undefined);
        assert.ok(result.contentBlockingRules.length > 0);
        assert.ok('trigger' in result.contentBlockingRules[0]);
        assert.ok('action' in result.contentBlockingRules[0]);
        assert.ok(result.filtersUsed.includes('||ads.example.com^'));
    });

    it('returns undefined when debug=false', () => {
        const fs = new FilterSet(false);
        fs.addFilters('||ads.example.com^');
        assert.equal(fs.intoContentBlocking(), undefined);
    });
});

// ---------------------------------------------------------------------------
// FilterSet clone semantics
// ---------------------------------------------------------------------------

describe('FilterSet survives Engine construction (clone semantics)', () => {
    it('FilterSet is still usable after being passed to Engine constructor', () => {
        const fs = new FilterSet();
        fs.addFilters('||example.com^');
        const _engine = new Engine(fs);
        assert.doesNotThrow(() => fs.addFilters('||another.com^'));
    });
});

// ---------------------------------------------------------------------------
// Engine.check — basic blocking
// ---------------------------------------------------------------------------

describe('Engine.check — basic blocking', () => {
    it('blocks matching requests, allows non-matching', () => {
        const fs = new FilterSet();
        fs.addFilters('||ads.example.com^');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://ads.example.com/t.js', 'https://pub.com', 'script'), true);
        assert.equal(engine.check('https://safe.com/t.js', 'https://pub.com', 'script'), false);
    });

    it('debug=true returns BlockerResult with filter text', () => {
        const fs = new FilterSet(true);
        fs.addFilters('||ads.example.com^');
        const engine = new Engine(fs);
        const result = engine.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true
        );
        assert.equal(result.should_block, true);
        assert.deepEqual(result.filter, {
          raw_line: '||ads.example.com^',
          source_location: {
            line_number: 0,
            source_index: 0
          }
        });
    });

    it('throws for an invalid URL', () => {
        const engine = new Engine(new FilterSet());
        assert.throws(() => engine.check('not a url', 'https://publisher.com', 'script'));
    });

    it('ignores legacy optimize constructor argument', () => {
        const fs = new FilterSet();
        fs.addFilters('||blocked.com^');
        const engine = new Engine(fs, true);
        assert.equal(
            engine.check('https://blocked.com/img.png', 'https://pub.com', 'image'),
            true,
        );
    });
});

// ---------------------------------------------------------------------------
// Engine.check — exception rules
// ---------------------------------------------------------------------------

describe('Engine.check — exception rules', () => {
    it('exception rule prevents blocking, populates exception field', () => {
        const fs = new FilterSet(true);
        fs.addFilters(['||ads.example.com^', '@@||ads.example.com^$domain=publisher.com'].join('\n'));
        const engine = new Engine(fs);
        const result = engine.check(
            'https://ads.example.com/tracker.js', 'https://publisher.com', 'script', 'get', true,
        );
        assert.equal(result.should_block, false);
        assert.deepEqual(result.exception, {
          raw_line: '@@||ads.example.com^$domain=publisher.com',
          source_location: {
            line_number: 1,
            source_index: 0
          }
        });
    });

    it('$important overrides exception rules', () => {
        const fs = new FilterSet(true);
        fs.addFilters(['||ads.example.com^$important', '@@||ads.example.com^'].join('\n'));
        const engine = new Engine(fs);
        const result = engine.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true,
        );
        assert.equal(result.should_block, true);
        assert.equal(result.important, true);
        assert.deepEqual(result.filter, {
          raw_line: '||ads.example.com^$important',
          source_location: {
            line_number: 0,
            source_index: 0
          }
        });
    });
});

// ---------------------------------------------------------------------------
// Engine.check — $third-party and $1p modifiers
// ---------------------------------------------------------------------------

describe('Engine.check — $third-party and $1p modifiers', () => {
    it('$third-party rule blocks 3p, allows 1p', () => {
        const fs = new FilterSet();
        fs.addFilters('||tracker.com^$third-party');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://tracker.com/t.js', 'https://other.com', 'script'), true);
        assert.equal(engine.check('https://tracker.com/t.js', 'https://tracker.com', 'script'), false);
    });

    it('$1p rule blocks 1p, allows 3p', () => {
        const fs = new FilterSet();
        fs.addFilters('/bad-path$1p');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://example.com/bad-path', 'https://example.com', 'script'), true);
        assert.equal(engine.check('https://example.com/bad-path', 'https://other.com', 'script'), false);
    });
});

// ---------------------------------------------------------------------------
// Engine.check — type-specific rules
// ---------------------------------------------------------------------------

describe('Engine.check — type-specific rules', () => {
    it('$script blocks only script requests', () => {
        const fs = new FilterSet();
        fs.addFilters('||ads.example.com^$script');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://ads.example.com/t.js', 'https://pub.com', 'script'), true);
        assert.equal(engine.check('https://ads.example.com/t.png', 'https://pub.com', 'image'), false);
    });

    it('$image blocks only image requests', () => {
        const fs = new FilterSet();
        fs.addFilters('||ads.example.com^$image');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://ads.example.com/t.png', 'https://pub.com', 'image'), true);
        assert.equal(engine.check('https://ads.example.com/t.js', 'https://pub.com', 'script'), false);
    });

    it('$stylesheet blocks only stylesheet requests', () => {
        const fs = new FilterSet();
        fs.addFilters('||ads.example.com^$stylesheet');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://ads.example.com/s.css', 'https://pub.com', 'stylesheet'), true);
        assert.equal(engine.check('https://ads.example.com/s.js', 'https://pub.com', 'script'), false);
    });

    it('$xmlhttprequest blocks only XHR requests', () => {
        const fs = new FilterSet();
        fs.addFilters('||ads.example.com^$xmlhttprequest');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://ads.example.com/api', 'https://pub.com', 'xmlhttprequest'), true);
        assert.equal(engine.check('https://ads.example.com/api', 'https://pub.com', 'image'), false);
    });

    it('$method=post blocks only POST requests', () => {
        const fs = new FilterSet();
        fs.addFilters('||api.example.com^$xhr,method=post');
        const engine = new Engine(fs);
        const url = 'https://api.example.com/collect';
        const source = 'https://pub.com';
        assert.equal(engine.check(url, source, 'xhr', 'post'), true);
        assert.equal(engine.check(url, source, 'xhr', 'get'), false);
        assert.equal(engine.check(url, source, 'xhr'), false);
    });

    it('debug=true remains backward compatible as 4th boolean arg', () => {
        const fs = new FilterSet(true);
        fs.addFilters('||api.example.com^$xhr,method=post');
        const engine = new Engine(fs, true);
        const result = engine.check(
            'https://api.example.com/collect', 'https://pub.com', 'xhr', true,
        );
        assert.equal(result.should_block, false);
    });
});

// ---------------------------------------------------------------------------
// Engine.check — $domain modifier
// ---------------------------------------------------------------------------

describe('Engine.check — $domain modifier', () => {
    it('blocks only when source matches the domain option', () => {
        const fs = new FilterSet();
        fs.addFilters('/ads.js$domain=publisher.com');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://cdn.example.com/ads.js', 'https://publisher.com', 'script'), true);
        assert.equal(engine.check('https://cdn.example.com/ads.js', 'https://other.com', 'script'), false);
    });

    it('~domain excludes the specified domain', () => {
        const fs = new FilterSet();
        fs.addFilters('/ads.js$domain=~safe.com');
        const engine = new Engine(fs);
        assert.equal(engine.check('https://cdn.example.com/ads.js', 'https://safe.com', 'script'), false);
        assert.equal(engine.check('https://cdn.example.com/ads.js', 'https://other.com', 'script'), true);
    });
});

// ---------------------------------------------------------------------------
// Engine.check — $badfilter modifier
// ---------------------------------------------------------------------------

describe('Engine.check — $badfilter modifier', () => {
    it('cancels a matching blocking rule', () => {
        const fs = new FilterSet();
        fs.addFilters(['||ads.example.com^', '||ads.example.com^$badfilter'].join('\n'));
        const engine = new Engine(fs);
        assert.equal(engine.check('https://ads.example.com/t.js', 'https://pub.com', 'script'), false);
    });

    it('does not cancel a dissimilar rule', () => {
        const fs = new FilterSet();
        fs.addFilters(['||ads.example.com^', '||other.com^$badfilter'].join('\n'));
        const engine = new Engine(fs);
        assert.equal(engine.check('https://ads.example.com/t.js', 'https://pub.com', 'script'), true);
    });
});

// ---------------------------------------------------------------------------
// Engine.check — redirect rules
// ---------------------------------------------------------------------------

describe('Engine.check — redirect rules', () => {
    it('redirect field is set when $redirect rule matches and resource is loaded', () => {
        const fs = new FilterSet(true);
        fs.addFilters('||ads.example.com^$script,redirect=noopjs');
        const engine = new Engine(fs);
        engine.useResources([{
            name: 'noopjs',
            aliases: [],
            kind: { mime: 'application/javascript' },
            content: btoa('(function(){})()'),
        }]);
        const result = engine.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true,
        );
        assert.equal(result.should_block, true);
        assert.deepEqual(result.filter, {
          raw_line: '||ads.example.com^$script,redirect=noopjs',
          source_location: {
            line_number: 0,
            source_index: 0
          }
        });
        assert.ok(result.redirect.length > 0);
    });

    it('redirect is null when no redirect rule applies', () => {
        const fs = new FilterSet(true);
        fs.addFilters('||ads.example.com^');
        const engine = new Engine(fs);
        const result = engine.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true,
        );
        assert.equal(result.should_block, true);
        assert.deepEqual(result.filter, {
          raw_line: '||ads.example.com^',
          source_location: {
            line_number: 0,
            source_index: 0
          }
        });
        assert.ok(result.redirect == null);
    });
});

// ---------------------------------------------------------------------------
// Engine.check — $removeparam modifier
// ---------------------------------------------------------------------------

describe('Engine.check — $removeparam modifier', () => {
    it('strips the specified parameter, preserves others', () => {
        const fs = new FilterSet(true);
        fs.addFilters('||example.com^$removeparam=tracking_id');
        const engine = new Engine(fs);
        const result = engine.check(
            'https://example.com/page?tracking_id=abc&keep=1',
            'https://other.com', 'xmlhttprequest', 'get', true,
        );
        assert.ok(result.rewritten_url != null);
        assert.ok(!result.rewritten_url.includes('tracking_id'));
        assert.ok(result.rewritten_url.includes('keep=1'));
    });

    it('rewritten_url is null when the parameter is absent', () => {
        const fs = new FilterSet(true);
        fs.addFilters('||example.com^$removeparam=tracking_id');
        const engine = new Engine(fs);
        const result = engine.check(
            'https://example.com/page?unrelated=1',
            'https://other.com', 'xmlhttprequest', 'get', true,
        );
        assert.ok(result.rewritten_url == null);
    });
});

// ---------------------------------------------------------------------------
// Engine.check — exception rules with tags
// ---------------------------------------------------------------------------

describe('Engine.check — exception rules with tags', () => {
    it('tagged exception activates only after enableTag', () => {
        const fs = new FilterSet(true);
        fs.addFilters(['||ads.example.com^', '@@||ads.example.com^$tag=unbreak'].join('\n'));
        const engine = new Engine(fs);

        const before = engine.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true,
        );
        assert.equal(before.should_block, true);
        assert.ok(before.exception == null);

        engine.enableTag('unbreak');
        const after = engine.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true,
        );
        assert.equal(after.should_block, false);
        assert.deepEqual(after.exception, {
          raw_line: '@@||ads.example.com^$tag=unbreak',
          source_location: {
            line_number: 1,
            source_index: 0
          }
        });
    });
});

// ---------------------------------------------------------------------------
// Engine.urlCosmeticResources
// ---------------------------------------------------------------------------

describe('Engine.urlCosmeticResources', () => {
    it('returns matching hide_selectors for the URL', () => {
        const fs = new FilterSet();
        fs.addFilters(['example.com##.ad-banner', 'example.com##.sponsored-post'].join('\n'));
        const engine = new Engine(fs);
        const result = engine.urlCosmeticResources('https://example.com/page');
        assert.ok(result.hide_selectors.includes('.ad-banner'));
        assert.ok(result.hide_selectors.includes('.sponsored-post'));
    });

    it('returns empty hide_selectors for an unmatched URL', () => {
        const fs = new FilterSet();
        fs.addFilters('example.com##.ad-banner');
        const engine = new Engine(fs);
        const result = engine.urlCosmeticResources('https://other-site.com/page');
        assert.equal(result.hide_selectors.length, 0);
    });

    it('generichide exception sets generichide=true', () => {
        const fs = new FilterSet();
        fs.addFilters(['##.generic-ad', '@@||example.com^$generichide'].join('\n'));
        const engine = new Engine(fs);
        assert.equal(engine.urlCosmeticResources('https://example.com/page').generichide, true);
        assert.equal(engine.urlCosmeticResources('https://other.com/page').generichide, false);
    });

    it('site-specific unhide (#@#) prevents selector from appearing', () => {
        const fs = new FilterSet();
        fs.addFilters(['example.com##.ad-banner', 'example.com#@#.ad-banner'].join('\n'));
        const engine = new Engine(fs);
        const result = engine.urlCosmeticResources('https://example.com/page');
        assert.ok(!result.hide_selectors.includes('.ad-banner'));
    });

    it('generic unhide (#@#) adds to exceptions list', () => {
        const fs = new FilterSet();
        fs.addFilters(['##.generic-ad', 'example.com#@#.generic-ad'].join('\n'));
        const engine = new Engine(fs);
        const result = engine.urlCosmeticResources('https://example.com/page');
        assert.ok(result.exceptions.includes('.generic-ad'));
    });

    it('negated domain (~sub.example.com) excludes subdomain', () => {
        const fs = new FilterSet();
        fs.addFilters('example.com,~sub.example.com##.ad');
        const engine = new Engine(fs);
        assert.ok(engine.urlCosmeticResources('https://example.com/page').hide_selectors.includes('.ad'));
        assert.ok(!engine.urlCosmeticResources('https://sub.example.com/page').hide_selectors.includes('.ad'));
    });

    it('procedural_actions populated for :has-text() and :remove() filters', () => {
        const fs = new FilterSet();
        fs.addFilters([
            'example.com##.items:has-text(Sponsored)',
            'example.com##.ad-banner:remove()',
        ].join('\n'));
        const engine = new Engine(fs);
        const result = engine.urlCosmeticResources('https://example.com/page');
        assert.ok(result.procedural_actions.length >= 2);
    });

    it('scriptlet injection populates injected_script', () => {
        const fs = new FilterSet();
        fs.addFilters('example.com##+js(noopjs)');
        const engine = new Engine(fs);
        // ##+js(noopjs) looks up "noopjs.js"; scriptlets use kind: "template"
        engine.useResources([{
            name: 'noopjs.js',
            aliases: [],
            kind: 'template',
            content: btoa('(function(){})()'),
        }]);
        const result = engine.urlCosmeticResources('https://example.com/page');
        assert.ok(result.injected_script.length > 0);
    });
});

// ---------------------------------------------------------------------------
// Engine.hiddenClassIdSelectors
// ---------------------------------------------------------------------------

describe('Engine.hiddenClassIdSelectors', () => {
    it('returns selectors matching class and id names', () => {
        const fs = new FilterSet();
        fs.addFilters(['##.a-class', '###simple-id'].join('\n'));
        const engine = new Engine(fs);
        assert.ok(engine.hiddenClassIdSelectors(['a-class'], [], []).includes('.a-class'));
        assert.ok(engine.hiddenClassIdSelectors([], ['simple-id'], []).includes('#simple-id'));
    });

    it('returns empty for unknown class/id names', () => {
        const fs = new FilterSet();
        fs.addFilters('##.a-class');
        const engine = new Engine(fs);
        assert.deepEqual(engine.hiddenClassIdSelectors(['unknown'], ['unknown'], []), []);
    });

    it('exceptions array filters out results', () => {
        const fs = new FilterSet();
        fs.addFilters('##.a-class');
        const engine = new Engine(fs);
        assert.ok(engine.hiddenClassIdSelectors(['a-class'], [], []).includes('.a-class'));
        assert.ok(!engine.hiddenClassIdSelectors(['a-class'], [], ['.a-class']).includes('.a-class'));
    });
});

// ---------------------------------------------------------------------------
// Engine serialization
// ---------------------------------------------------------------------------

describe('Engine serialization', () => {
    it('roundtrip preserves blocking and exception rules', () => {
        const fs = new FilterSet();
        fs.addFilters(['||blocked.com^', '@@||exception.blocked.com^'].join('\n'));
        const src = new Engine(fs);
        const buf = src.serialize();

        const dst = new Engine(new FilterSet());
        dst.deserialize(buf);
        assert.equal(dst.check('https://blocked.com/img.png', 'https://pub.com', 'image'), true);
        assert.equal(dst.check('https://safe.com/img.png', 'https://pub.com', 'image'), false);
        assert.equal(dst.check('https://exception.blocked.com/', 'https://pub.com', 'other'), false);
    });

    it('tag enablement is NOT serialized — must re-enable after deserialize', () => {
        const fs = new FilterSet();
        fs.addFilters(['adv$tag=stuff', '||blocked.com^'].join('\n'));
        const src = new Engine(fs);
        src.enableTag('stuff');
        const buf = src.serialize();

        const dst = new Engine(new FilterSet());
        dst.deserialize(buf);

        // Untagged filter works immediately
        assert.equal(dst.check('https://blocked.com/t.js', 'https://pub.com', 'script'), true);
        // Tagged filter inactive until re-enabled
        assert.equal(dst.check('https://example.com/adv', 'https://example.com', 'other'), false);
        dst.enableTag('stuff');
        assert.equal(dst.check('https://example.com/adv', 'https://example.com', 'other'), true);
    });

    it('resources are NOT serialized — must reload after deserialize', () => {
        const fs = new FilterSet(true);
        fs.addFilters(['||ads.example.com^$script,redirect=noopjs'].join('\n'));
        const resource = {
            name: 'noopjs',
            aliases: [],
            kind: { mime: 'application/javascript' },
            content: btoa('(function(){})()'),
        };
        const src = new Engine(fs);
        src.useResources([resource]);
        const buf = src.serialize();

        const dst = new Engine(new FilterSet());
        dst.deserialize(buf);

        // Without reloading: redirect absent
        const without = dst.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true,
        );
        assert.equal(without.should_block, true);
        assert.ok(without.redirect == null);

        // After reloading: redirect works
        dst.useResources([resource]);
        const withRes = dst.check(
            'https://ads.example.com/t.js', 'https://pub.com', 'script', 'get', true,
        );
        assert.ok(withRes.redirect.length > 0);
    });
});

// ---------------------------------------------------------------------------
// Engine tags
// ---------------------------------------------------------------------------

describe('Engine tags', () => {
    it('tagged filter is inactive before enableTag, active after', () => {
        const fs = new FilterSet();
        fs.addFilters(['adv$tag=stuff'].join('\n'));
        const engine = new Engine(fs);
        assert.equal(engine.check('https://example.com/adv', 'https://example.com', 'other'), false);
        engine.enableTag('stuff');
        assert.equal(engine.check('https://example.com/adv', 'https://example.com', 'other'), true);
        assert.equal(engine.tagExists('stuff'), true);
    });

    it('clearTags deactivates all enabled tags', () => {
        const fs = new FilterSet();
        fs.addFilters(['adv$tag=stuff', '||brianbondy.com/$tag=brian'].join('\n'));
        const engine = new Engine(fs);
        engine.enableTag('stuff');
        engine.enableTag('brian');
        assert.equal(engine.check('https://example.com/adv', 'https://example.com', 'other'), true);

        engine.clearTags();
        assert.equal(engine.tagExists('stuff'), false);
        assert.equal(engine.tagExists('brian'), false);
        assert.equal(engine.check('https://example.com/adv', 'https://example.com', 'other'), false);
    });
});

// ---------------------------------------------------------------------------
// uBlockResources
// ---------------------------------------------------------------------------

describe('uBlockResources', () => {
    const dataDir = join(__dirname, '..', '..', 'data', 'test', 'fake-uBO-files');

    it('returns resources that work with engine.useResources', () => {
        const resources = uBlockResources(
            join(dataDir, 'web_accessible_resources'),
            join(dataDir, 'redirect-resources.js'),
        );
        assert.ok(resources.length > 0);
        const engine = new Engine(new FilterSet());
        assert.doesNotThrow(() => engine.useResources(resources));
    });

    it('accepts optional scriptlets path', () => {
        const resources = uBlockResources(
            join(dataDir, 'web_accessible_resources'),
            join(dataDir, 'redirect-resources.js'),
            join(dataDir, 'scriptlets.js'),
        );
        assert.ok(resources.length > 0);
    });
});
