browser_tester 1.5.0

Deterministic lightweight browser runtime for Rust tests
Documentation
use super::*;

#[test]
fn input_file_mock_is_decodable_with_create_image_bitmap_even_without_explicit_bytes() -> Result<()>
{
    let html = r#"
      <input id='file' type='file' accept='image/jpeg,image/png'>
      <p id='out'></p>
      <script>
        const input = document.getElementById('file');
        input.addEventListener('change', async () => {
          const file = input.files[0];
          try {
            const bmp = await createImageBitmap(file);
            document.getElementById('out').textContent = `ok:${bmp.width}x${bmp.height}`;
          } catch {
            document.getElementById('out').textContent = 'error';
          }
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    let mut file = MockFile::new("sample.jpg");
    file.size = 1024;
    file.mime_type = "image/jpeg".to_string();
    h.set_input_files("#file", &[file])?;
    h.assert_text("#out", "ok:1x1")?;
    Ok(())
}

#[test]
fn create_image_bitmap_reads_png_dimensions_from_mock_file_bytes() -> Result<()> {
    let html = r#"
      <input id='file' type='file' accept='image/png'>
      <p id='out'></p>
      <script>
        const input = document.getElementById('file');
        input.addEventListener('change', async () => {
          const file = input.files[0];
          const bmp = await createImageBitmap(file);
          document.getElementById('out').textContent = `${bmp.width}x${bmp.height}`;
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    let png_with_2x3_ihdr = [
        137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 2, 0, 0, 0, 3, 8, 2,
        0, 0, 0,
    ];
    let mut file = MockFile::new("shape.png").with_bytes(&png_with_2x3_ihdr);
    file.mime_type = "image/png".to_string();
    h.set_input_files("#file", &[file])?;
    h.assert_text("#out", "2x3")?;
    Ok(())
}

#[test]
fn create_image_bitmap_supports_crop_resize_and_image_bitmap_source() -> Result<()> {
    let html = r#"
      <canvas id='canvas' width='20' height='15'></canvas>
      <input id='file' type='file' accept='image/png'>
      <p id='out'></p>
      <script>
        const input = document.getElementById('file');
        input.addEventListener('change', async () => {
          try {
            const file = input.files[0];
            const base = await createImageBitmap(file);
            const crop = await createImageBitmap(file, 1, 2, 3, 4);
            const cropNeg = await createImageBitmap(file, 0, 0, -5, -6);
            const resized = await createImageBitmap(file, { resizeWidth: 7, resizeHeight: 9 });
            const cropResized = await createImageBitmap(file, 0, 0, 3, 4, {
              resizeWidth: 11,
              resizeHeight: 13
            });
            const clone = await createImageBitmap(base, { resizeWidth: 12 });
            const fromCanvas = await createImageBitmap(document.getElementById('canvas'));
            document.getElementById('out').textContent =
              `${base.width}x${base.height}|${crop.width}x${crop.height}|` +
              `${cropNeg.width}x${cropNeg.height}|${resized.width}x${resized.height}|` +
              `${cropResized.width}x${cropResized.height}|${clone.width}x${clone.height}|` +
              `${fromCanvas.width}x${fromCanvas.height}`;
          } catch (e) {
            document.getElementById('out').textContent = 'error:' + String(e);
          }
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    let png_with_10x8_ihdr = [
        137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 10, 0, 0, 0, 8, 8,
        2, 0, 0, 0,
    ];
    let mut file = MockFile::new("sheet.png").with_bytes(&png_with_10x8_ihdr);
    file.mime_type = "image/png".to_string();
    h.set_input_files("#file", &[file])?;
    h.assert_text("#out", "10x8|3x4|5x6|7x9|11x13|12x8|20x15")?;
    Ok(())
}

#[test]
fn create_image_bitmap_from_canvas_snapshots_dimensions_before_later_resize_work() -> Result<()> {
    let html = r#"
      <canvas id='canvas' width='20' height='15'></canvas>
      <button id='run'>run</button>
      <p id='out'></p>
      <script>
        document.getElementById('run').addEventListener('click', async () => {
          const canvas = document.getElementById('canvas');
          const pending = createImageBitmap(canvas);
          canvas.width = 40;
          canvas.height = 30;
          const bmp = await pending;
          document.getElementById('out').textContent =
            `${bmp.width}x${bmp.height}|${canvas.width}x${canvas.height}`;
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#out", "20x15|40x30")?;
    Ok(())
}

#[test]
fn reduced_create_image_bitmap_canvas_contract_snapshots_dimensions_before_resize_work()
-> Result<()> {
    let html = r#"
      <canvas id='canvas' width='12' height='9'></canvas>
      <button id='run'>run</button>
      <p id='out'></p>
      <script>
        document.getElementById('run').addEventListener('click', async () => {
          const canvas = document.getElementById('canvas');
          const promise = createImageBitmap(canvas);
          canvas.width = 24;
          canvas.height = 18;
          const bitmap = await promise;
          document.getElementById('out').textContent =
            `${bitmap.width}x${bitmap.height}|${canvas.width}x${canvas.height}`;
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#out", "12x9|24x18")?;
    Ok(())
}

#[test]
fn create_image_bitmap_rejects_invalid_signatures_and_options() -> Result<()> {
    let html = r#"
      <input id='file' type='file' accept='image/png'>
      <p id='out'></p>
      <script>
        const input = document.getElementById('file');
        input.addEventListener('change', async () => {
          const file = input.files[0];
          const results = [];
          try {
            await createImageBitmap(file, 1, 2, 3);
            results.push('badA');
          } catch (e) {
            results.push(String(e).includes('supports 1, 2, 5, or 6 arguments'));
          }
          try {
            await createImageBitmap(file, { resizeWidth: 0 });
            results.push('badB');
          } catch (e) {
            results.push(String(e).includes('resizeWidth'));
          }
          try {
            await createImageBitmap(file, 0, 0, 0, 2);
            results.push('badC');
          } catch (e) {
            results.push(String(e).includes('crop width/height'));
          }
          try {
            await createImageBitmap(file, 0, 0, 2, 2, 42);
            results.push('badD');
          } catch (e) {
            results.push(String(e).includes('options must be an object'));
          }
          document.getElementById('out').textContent = results.join(':');
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    let png_with_2x3_ihdr = [
        137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 2, 0, 0, 0, 3, 8, 2,
        0, 0, 0,
    ];
    let mut file = MockFile::new("shape.png").with_bytes(&png_with_2x3_ihdr);
    file.mime_type = "image/png".to_string();
    h.set_input_files("#file", &[file])?;
    h.assert_text("#out", "true:true:true:true")?;
    Ok(())
}

#[test]
fn create_image_bitmap_exposes_image_bitmap_constructor_surface_and_branding() -> Result<()> {
    let html = r#"
      <input id='file' type='file' accept='image/png'>
      <p id='out'></p>
      <script>
        const input = document.getElementById('file');
        input.addEventListener('change', async () => {
          const file = input.files[0];
          const bmp = await createImageBitmap(file);
          const proto = ImageBitmap.prototype;
          const widthDesc = Object.getOwnPropertyDescriptor(proto, 'width');
          const heightDesc = Object.getOwnPropertyDescriptor(proto, 'height');
          const closeDesc = Object.getOwnPropertyDescriptor(proto, 'close');
          let illegal = '';
          try {
            ImageBitmap();
          } catch (e) {
            illegal = String(e);
          }
          document.getElementById('out').textContent = [
            typeof ImageBitmap,
            String(window.ImageBitmap === ImageBitmap),
            String(bmp.constructor === ImageBitmap),
            String(Object.getPrototypeOf(bmp) === ImageBitmap.prototype),
            String(Object.getPrototypeOf(ImageBitmap.prototype) === Object.prototype),
            Object.getOwnPropertyNames(ImageBitmap.prototype).sort().join(','),
            String(typeof widthDesc.get),
            String(widthDesc.enumerable),
            String(typeof heightDesc.get),
            String(typeof closeDesc.value),
            String(closeDesc.enumerable),
            Object.prototype.toString.call(bmp),
            String(illegal.includes('Illegal constructor'))
          ].join('|');
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    let png_with_2x3_ihdr = [
        137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 2, 0, 0, 0, 3, 8, 2,
        0, 0, 0,
    ];
    let mut file = MockFile::new("shape.png").with_bytes(&png_with_2x3_ihdr);
    file.mime_type = "image/png".to_string();
    h.set_input_files("#file", &[file])?;
    h.assert_text(
        "#out",
        "function|true|true|true|true|close,constructor,height,width|function|false|function|function|false|[object ImageBitmap]|true",
    )?;
    Ok(())
}

#[test]
fn image_bitmap_close_and_reflective_surface_support_extracted_calls_and_shadow_restore()
-> Result<()> {
    let html = r#"
      <input id='file' type='file' accept='image/png'>
      <p id='out'></p>
      <script>
        const input = document.getElementById('file');
        input.addEventListener('change', async () => {
          const file = input.files[0];
          const bmp = await createImageBitmap(file);
          const proto = ImageBitmap.prototype;
          const widthGetter = Object.getOwnPropertyDescriptor(proto, 'width').get;
          const heightGetter = Object.getOwnPropertyDescriptor(proto, 'height').get;
          const close = bmp.close;
          let widthError = '';
          let closeError = '';
          try {
            widthGetter.call({});
          } catch (e) {
            widthError = String(e);
          }
          try {
            close.call({});
          } catch (e) {
            closeError = String(e);
          }

          const before = [
            String(close === ImageBitmap.prototype.close),
            String(widthGetter.call(bmp)),
            String(heightGetter.call(bmp)),
            String(Object.keys(bmp).length),
            String(Object.keys(Object.assign({}, bmp)).length),
            String(Object.keys({ ...bmp }).length)
          ].join(':');

          Object.defineProperty(ImageBitmap, 'marker', {
            value: 'ctor',
            enumerable: true,
            configurable: true
          });
          Object.defineProperty(ImageBitmap.prototype, 'marker', {
            value: 'proto',
            enumerable: true,
            configurable: true
          });
          Object.defineProperty(bmp, 'width', {
            value: 'shadow-width',
            enumerable: true,
            configurable: true
          });
          Object.defineProperty(bmp, 'marker', {
            value: 'bitmap',
            enumerable: true,
            configurable: true
          });

          const shadowed = [
            ImageBitmap.marker,
            ImageBitmap.prototype.marker,
            bmp.width,
            bmp.marker,
            Object.keys(ImageBitmap).join(','),
            Object.keys(ImageBitmap.prototype).join(','),
            Object.keys(bmp).sort().join(','),
            Object.assign({}, bmp).width,
            Object.assign({}, bmp).marker
          ].join(':');

          delete ImageBitmap.marker;
          delete ImageBitmap.prototype.marker;
          delete bmp.width;
          delete bmp.marker;
          close.call(bmp);

          const restored = [
            String(Object.hasOwn(ImageBitmap, 'marker')),
            String(Object.hasOwn(ImageBitmap.prototype, 'marker')),
            String(Object.hasOwn(bmp, 'width')),
            String(Object.hasOwn(bmp, 'marker')),
            String(widthGetter.call(bmp)),
            String(heightGetter.call(bmp)),
            String(widthError.includes('ImageBitmap method called on incompatible receiver')),
            String(closeError.includes('ImageBitmap method called on incompatible receiver')),
            String(Object.keys(bmp).length)
          ].join(':');

          document.getElementById('out').textContent = [before, shadowed, restored].join('|');
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    let png_with_2x3_ihdr = [
        137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 2, 0, 0, 0, 3, 8, 2,
        0, 0, 0,
    ];
    let mut file = MockFile::new("shape.png").with_bytes(&png_with_2x3_ihdr);
    file.mime_type = "image/png".to_string();
    h.set_input_files("#file", &[file])?;
    h.assert_text(
        "#out",
        "true:2:3:0:0:0|ctor:proto:shadow-width:bitmap:marker:marker:marker,width:shadow-width:bitmap|false:false:false:false:0:0:true:true:0",
    )?;
    Ok(())
}