spec: |
  <html>

  <head>
  <title>%name% // WebGL2DContext conformance</title>
  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">

  <style>
    div {
      padding: 5px;
    }
    canvas {
      border: 1px black solid;
    }
    .progress {
      font: 100% monospace;
    }
    .failure {
      background-color: red;
      color: white;
      font: 100% sans-serif;
    }
    .success {
      background-color: #88FF88;
      font: 100% sans-serif;
    }
    .failure a:link {
      color: white
    }
    .failure a:visited {
      color: white
    }
  </style>

  <script type="text/javascript" src="bundle.js"></script>
  <script type="text/javascript" src="assets.js"></script>
  <script type="text/javascript" src="asserts.js"></script>

  <script type="text/javascript">
      window.ImageData = ImageData;

      async function getAsset(name) {
        return new Promise((resolve, reject) => {
          let img = document.createElement("IMG")
          img.onload = () => resolve(img)
          img.onerror = reject
          img.src = rawImageAssets[name]
        })
      }

      function bootCanvas(container, id, width, height, needsHighBitDepth) {
          var canvas = document.createElement("CANVAS")
          canvas.setAttribute("id", id)
          canvas.setAttribute("width", width)
          canvas.setAttribute("height", height)

          container.appendChild(canvas)

          var gl = canvas.getContext("webgl2", {
            stencil: 8,
            preserveDrawingBuffer: true
          });
          gl.viewportWidth = canvas.width;
          gl.viewportHeight = canvas.height;
          if (!gl) {
              console.log("Could not initialise WebGL, sorry :-(")
              return null;
          }

          gl.clearColor(1.0, 0.0, 0.0, 1.0);
          gl.clear(gl.COLOR_BUFFER_BIT);

          /* TODO: figure out how to pass some gradient tests that need
           * more grad stops: */
          let ctx = new Expo2DContext.default(gl, {maxGradStops: 50, renderWithOffscreenBuffer: needsHighBitDepth})
          ctx.getContext = () => {return ctx;}
          
          return [canvas, gl, ctx]
      }

      function spinner_graphic(i) {
        let anim = ["/","&ndash;","\\","|","/","&ndash;","\\", "|"]
        return anim[i%anim.length]
      }

      var testContexts = {};

      function focusTest (num) {
        window.ctx = testContexts[num][0]
        window.allContexts = testContexts[num]
      }
      window.onhashchange = () => {
        var url = new URL(window.location)
        var hashVal = url.hash.substr(1)
        if (hashVal in testContexts) {
          focusTest(hashVal)
        }
      }

      async function runTests() {

        // TODO: assets??

        if (!window.testComplete) {
          window.testComplete = (testResults, completedTests, totalTests) =>
            { /* PUPPETEER STUB */ };
        }

        testContexts = {};

        var tests = document.getElementsByClassName("test")

        var url = new URL(window.location)
        if (url.searchParams.get("runTests") != null) {
          selected_test_ids = url.searchParams.get("runTests").split(",").map((x) => parseInt(x))
          selected_test_ids = selected_test_ids.filter((elm, idx) => {return isFinite(elm)})
          tests = selected_test_ids.map((x) => document.getElementById("test_"+x+"_content")) 
        }

        var cleanUp = url.searchParams.get("noCleanup") == null

        let suite_status = document.getElementById("suite_status")

        var failures = new Set();
        var failureSummaryHTML = "Failed:<ul>"

        let i = 0;

        var testIteration = async (i) => {

          if (i >= tests.length) {
            finishTests();
            return;
          }

          let test_area = tests[i]
          let test = test_area.dataset
                
          let allCanvases = []
          let allWebGLContexts = []
          let allContexts = []
          
          let failureMessages = []

          let test_fn = eval("test_"+test.id+"_body");
          let needsHighBitDepth = test_fn.toString().includes("getImageData");
 
          for (let j=0; j<parseInt(test.contexts); j++) {

            let [canvas, gl, ctx] = bootCanvas(
              test_area,
              "canvas_"+test.id+"_"+j,
              parseInt(test.width),
              parseInt(test.height),
              needsHighBitDepth
            )
            // Install fake getContext wrapper to allow context objects to be
            // used in place of canvas objects for our tests:
            ctx.getContext = (unused) => {return ctx};
            allCanvases.push(canvas)
            allWebGLContexts.push(gl)
            allContexts.push(ctx)
          }

          let onFail = TriggerObject((message) => {
            let status_box = document.getElementById("test_"+test.id+"_status")

            status_box.setAttribute("class","failure")
            status_box.innerHTML += message + "<br />";
            failureMessages.push(message)

            if (!failures.has(test.id)) {
              failureSummaryHTML += "<li><a href='#"+test.id+"'>"+test.id+": "+test.description+"</a></li>"
              failures.add(test.id);
            }
          })
 
          try {
            await test_fn(onFail, allCanvases, allContexts);
          } catch (err) {
            onFail.trigger(false, err.toString());
          }

          if (cleanUp) {
            // Copy the resulting drawings out of the original canvas and
            // safely trash the old gl context, to free up resources:
            for (let j=0; j<parseInt(test.contexts); j++) {
              let canvas_clone = allCanvases[j].cloneNode(true);
              let clone_ctx = canvas_clone.getContext('2d');
              clone_ctx.drawImage(allCanvases[j], 0, 0);

              allCanvases[j].replaceWith(canvas_clone);

              allWebGLContexts[j].getExtension('WEBGL_lose_context').loseContext();
              allWebGLContexts[j] = null;
            }
          } else {
            testContexts[test.id] = allContexts
          }

          suite_status.innerHTML = "["+ spinner_graphic(i) + "] Tests run: " + (i+1) + " / " + tests.length;

          window.testComplete(
            Object.assign({
              "failureMessages": failureMessages
            }, test), i+1, tests.length);

          setTimeout(() => { testIteration(i+1) }, 1);
        }

        var finishTests = () => {
          if (failures.size > 0) {
            failureSummaryHTML = (tests.length-failures.size) + "/" + tests.length + " tests passed<br />"+failureSummaryHTML 
            failureSummaryHTML += "</ul><br />"
            suite_status.setAttribute("class","failure")
            suite_status.innerHTML = failureSummaryHTML
          } else {
            suite_status.setAttribute("class","success")
            suite_status.innerHTML = "[:)] All tests pass !" 
          }

          if (!cleanUp && tests.length==1){
            focusTest(tests[0].dataset.id)
          } else {
            window.onhashchange()
          }
        };

        testIteration(0);

      }

  </script>
  </head>
  <body onload="runTests();">
  <h1> %name% </h1>
  <div id="suite_status" class="progress">&nbsp;</div>
  <hr />

  %tests%

  <br /><br />
  &lt;3

  </body>
  </html>


test: |
  <p><a id="%id%" href="#%id%"\>%id%</a>: %description%</p>
  
  <script>
    async function test_%id%_body(t, allCanvases, allContexts) {
      %init%
      
      let ctx = allContexts[0];
      let canvas = allCanvases[0];

      %body%
    }
  </script>

  <div class="test"
    id="test_%id%_content"
    data-contexts="%totalContexts%"
    data-id="%id%"
    data-name="%name%"
    data-width="%width%"
    data-height="%height%"
    data-description="%description%">&nbsp;</div><br />

  <div id="test_%id%_status">&nbsp;</div>

  [<a href="?runTests=%id%&noCleanup#%id%" onclick="window.location.reload()">solo run</a>]
  
  <hr />

asset_definition: |
  "%asset_filename%": "assets/%asset_filename%"

assets: |
  var rawImageAssets = {
    %assetlist%
  }

index_item: |
        <li><a data-name="2d.%name%" data-count="%count%" href="%filename%">2d.%name%</a> (%count% tests)</li>

index: |
  <html><body>
    <ul>
    %indexlist%
    </ul>
  </body></html>

filenames: |
  %name%.html

disabled: |
  <!-- DISABLED: %reason%
  %body%
  -->
