- name: 2d.state.saverestore.transformation
  desc: save()/restore() affects the current transformation matrix
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.save();
    ctx.translate(200, 0);
    ctx.restore();
    ctx.fillStyle = '#f00';
    ctx.fillRect(-200, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.state.saverestore.clip
  desc: save()/restore() affects the clipping path
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.save();
    ctx.rect(0, 0, 1, 1);
    ctx.clip();
    ctx.restore();
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.state.saverestore.clip.2
  desc: save()/restore() affects the clipping path
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.save();
    ctx.rect(0, 0, 1, 1);
    ctx.clip();
    ctx.clip();
    ctx.restore();
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.state.saverestore.path
  desc: save()/restore() does not affect the current path
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.save();
    ctx.rect(0, 0, 100, 50);
    ctx.restore();
    ctx.fillStyle = '#0f0';
    ctx.fill();
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.state.saverestore.bitmap
  desc: save()/restore() does not affect the current bitmap
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.save();
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.restore();
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.state.saverestore.stack
  desc: save()/restore() can be nested as a stack
  code: |
    ctx.lineWidth = 1;
    ctx.save();
    ctx.lineWidth = 2;
    ctx.save();
    ctx.lineWidth = 3;
    @assert ctx.lineWidth === 3;
    ctx.restore();
    @assert ctx.lineWidth === 2;
    ctx.restore();
    @assert ctx.lineWidth === 1;

- name: 2d.state.saverestore.stackdepth
  desc: save()/restore() stack depth is not unreasonably limited
  code: |
    var limit = 512;
    for (var i = 1; i < limit; ++i)
    {
        ctx.save();
        ctx.lineWidth = i;
    }
    for (var i = limit-1; i > 0; --i)
    {
        @assert ctx.lineWidth === i;
        ctx.restore();
    }

- name: 2d.state.saverestore.underflow
  desc: restore() with an empty stack has no effect
  code: |
    for (var i = 0; i < 16; ++i)
        ctx.restore();
    ctx.lineWidth = 0.5;
    ctx.restore();
    @assert ctx.lineWidth === 0.5;

- name: 2d.state.saverestore
  variants_layout: [single_file, single_file, multi_files]
  variants:
  - restore-undoes-changes:
      desc: save()/restore() restores {{ variant_names[2] }}, for a canvas of
            size {{ size }}.
      code: |
        const old = ctx.{{ variant_names[2] }};
        ctx.save();
        ctx.{{ variant_names[2] }} = {{ value }};
        ctx.restore();
        @assert ctx.{{ variant_names[2] }} === old;
    save-does-not-modify-values:
      desc: save() does not modify {{ variant_names[2] }}, for a canvas of size
            {{ size }}.
      code: |
        ctx.{{ variant_names[2] }} = {{ value }};
        const old = ctx.{{ variant_names[2] }};
        // We're not interested in failures caused by get(set(x)) != x (e.g.
        // from rounding), so compare against `old` instead of {{ value }}.
        ctx.save();
        @assert ctx.{{ variant_names[2] }} === old;
        ctx.restore();
  - non-zero-size:
      size: [300, 150]
    zero-size:
      size: [0, 0]
  - &2d_state_test_cases
    strokeStyle:
      value: '"#ff0000"'
    fillStyle:
      value: '"#ff0000"'
    globalAlpha:
      value: 0.5
    lineWidth:
      value: 0.5
    lineCap:
      value: '"round"'
    lineJoin:
      value: '"round"'
    miterLimit:
      value: 0.5
    shadowOffsetX:
      value: 5
    shadowOffsetY:
      value: 5
    shadowBlur:
      value: 5
    shadowColor:
      value: '"#ff0000"'
    globalCompositeOperation:
      value: '"copy"'
    font:
      canvas_types: ['HtmlCanvas']
      value: '"25px serif"'
    textAlign:
      canvas_types: ['HtmlCanvas']
      value: '"center"'
    textBaseline:
      canvas_types: ['HtmlCanvas']
      value: '"bottom"'

- name: 2d.canvas.host.initial.reset.2dstate
  desc: Resetting the canvas state resets {{ variant_name }}
  code: |
    const default_val = ctx.{{ variant_name }};
    ctx.{{ variant_name }} = {{ value }};
    canvas.width = 100;
    @assert ctx.{{ variant_name }} === default_val;
  variants_layout: [single_file]
  variants:
  - *2d_state_test_cases
