import { fromContents } from "../../lib-js/spec-directory"
import { mockCompiler } from "../fixtures/mock-compiler"
import TestCase, { TestResult } from "../../lib-js/test-case"

// TODO most of these tests can be factored out into tests that comparing two results work
describe("TestCase::result()", () => {
  async function runTestCase(
    content: string,
    opts: any = {}
  ): Promise<TestResult> {
    const dir = await fromContents(content.trim())
    const test = await TestCase.create(
      dir,
      "sass-mock",
      mockCompiler(dir),
      opts.todoMode
    )
    return test.result()
  }

  describe("success cases", () => {
    it("passes if the outputs match", async () => {
      const input = `
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
<===> output.css
p {
  color: #ff8000;
}`
      expect(await runTestCase(input)).toMatchObject({ type: "pass" })
    })

    it("fails if the outputs are mismatched", async () => {
      const input = `
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
<===> output.css
p {
  color: #000000;
}`
      expect(await runTestCase(input)).toMatchObject({
        type: "fail",
        failureType: "output_difference",
      })
    })

    it("fails if the spec throws an error", async () => {
      const input = `
<===> input.scss
status: 1
stderr: |
  Error: expected ")".
<===> output.css
p {
  color: #000000;
}`
      expect(await runTestCase(input)).toMatchObject({
        type: "fail",
        failureType: "unexpected_error",
      })
    })
  })

  describe("error cases", () => {
    const input = `
<===> input.scss
status: 1
stderr: |
  Error: expected ")".
<===> error
Error: expected ")".
`
    it("passes when the errors match", async () => {
      expect(await runTestCase(input)).toMatchObject({ type: "pass" })
    })

    it("fails on mismatched errors", async () => {
      const input = `
<===> input.scss
status: 1
stderr: |
  Error: expected ")".
<===> error
Error: expected "(".
`
      expect(await runTestCase(input)).toMatchObject({
        type: "fail",
        failureType: "error_difference",
      })
    })

    it("fails if the test case passes", async () => {
      const input = `
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
<===> error
Error: expected "(".
`
      expect(await runTestCase(input)).toMatchObject({
        type: "fail",
        failureType: "unexpected_success",
      })
    })
  })

  describe("warning cases", () => {
    it("passes when the warnings match", async () => {
      const input = `
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
stderr: |
  WARNING: test
<===> output.css
p {
  color: #ff8000;
}
<===> warning
WARNING: test
`
      expect(await runTestCase(input)).toMatchObject({ type: "pass" })
    })

    it("fails when the warnings are different", async () => {
      const input = `
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
stderr: |
  WARNING: failure
<===> output.css
p {
  color: #ff8000;
}
<===> warning
WARNING: test
`
      expect(await runTestCase(input)).toMatchObject({
        type: "fail",
        failureType: "warning_difference",
      })
    })

    it("fails when expected warning is missing", async () => {
      const input = `
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
<===> output.css
p {
  color: #ff8000;
}
<===> warning
WARNING: test
`
      expect(await runTestCase(input)).toMatchObject({
        type: "fail",
      })
    })

    it("fails when extraneous warning is present", async () => {
      const input = `
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
stderr: |
  WARNING: test
<===> output.css
p {
  color: #ff8000;
}
`
      expect(await runTestCase(input)).toMatchObject({
        type: "fail",
      })
    })

    const todoInput = `
<===> options.yml
:warning_todo:
  - sass-mock
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
stderr: |
  WARNING: failure
<===> output.css
p {
  color: #ff8000;
}
<===> warning
WARNING: test
`

    it("skips warning checks if :warning_todo option enabled", async () => {
      expect(await runTestCase(todoInput)).toMatchObject({ type: "pass" })
    })

    it("runs warning check if `:warning_todo` is enabled but --run-todo is chosen", async () => {
      expect(await runTestCase(todoInput, { todoMode: "run" })).toMatchObject({
        type: "fail",
      })
    })

    it("marks a warning check as failed if it succeeds but --probe-todo is chosen", async () => {
      const input = `
<===> options.yml
:warning_todo:
  - sass-mock
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
stderr: |
  WARNING: test
<===> output.css
p {
  color: #ff8000;
}
<===> warning
WARNING: test
`
      expect(await runTestCase(input, { todoMode: "probe" })).toMatchObject({
        type: "fail",
        failureType: "unnecessary_todo",
      })
    })
  })

  describe("ignore", () => {
    it("marks a test as `skip` if the `:ignore_for` option enabled", async () => {
      const input = `
<===> options.yml
:ignore_for:
  - sass-mock
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
<===> output.css
p {
  color: #000000;
}
`
      expect(await runTestCase(input)).toMatchObject({ type: "skip" })
    })
  })

  describe("todo", () => {
    const failInput = `
<===> options.yml
:todo:
  - sass-mock
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
<===> output.css
p {
  color: #000000;
}
`
    it("marks a test as `todo` when the `:todo` option is set", async () => {
      expect(await runTestCase(failInput)).toMatchObject({ type: "todo" })
    })

    it("runs todos if --run-todo is set", async () => {
      expect(await runTestCase(failInput, { todoMode: "run" })).toMatchObject({
        type: "fail",
      })
    })

    it("marks a failing todo case as `todo` if --probe-todo is set", async () => {
      expect(
        await runTestCase(failInput, { todoMode: "probe" })
      ).toMatchObject({ type: "todo" })
    })

    it("marks a passing todo case as a failure when --probe-todo is set", async () => {
      const passInput = `
<===> options.yml
:todo:
  - sass-mock
<===> input.scss
status: 0
stdout: |
  p {
    color: #ff8000;
  }
<===> output.css
p {
  color: #ff8000;
}
`
      expect(
        await runTestCase(passInput, { todoMode: "probe" })
      ).toMatchObject({ type: "fail", failureType: "unnecessary_todo" })
    })
  })

  describe("invalid specs", () => {
    it("returns an error type if the test case was invalid", async () => {
      const input = `
<===> input.scss
p {
  color: black;
}
<===> input.sass
p
  color: black
      `
      expect(await runTestCase(input)).toMatchObject({ type: "error" })
    })
  })
})
