;; SUnit port tests. Loads `lib/smalltalk/sunit.sx` (which itself calls
;; smalltalk-load to install TestCase/TestSuite/TestResult/TestFailure)
;; and exercises the framework on small Smalltalk-defined cases.

(set! st-test-pass 0)
(set! st-test-fail 0)
(set! st-test-fails (list))

;; test.sh loads lib/smalltalk/sunit.sx for us BEFORE this file runs
;; (nested SX loads do not propagate top-level forms reliably, so the
;; bootstrap chain is concentrated in test.sh). The SUnit classes are
;; already present in the class table at this point.

(define ev (fn (src) (smalltalk-eval src)))
(define evp (fn (src) (smalltalk-eval-program src)))

;; ── 1. Classes installed ──
(st-test "TestCase exists"      (st-class-exists? "TestCase")    true)
(st-test "TestSuite exists"     (st-class-exists? "TestSuite")   true)
(st-test "TestResult exists"    (st-class-exists? "TestResult")  true)
(st-test "TestFailure < Error"
  (st-class-inherits-from? "TestFailure" "Error") true)

;; ── 2. A subclass with one passing test runs cleanly ──
(smalltalk-load
  "TestCase subclass: #PassingCase
     instanceVariableNames: ''!

   !PassingCase methodsFor: 'tests'!
   testOnePlusOne self assert: 1 + 1 = 2! !")

(st-test "passing test runs and counts as pass"
  (evp
    "| suite r |
     suite := PassingCase suiteForAll: #(#testOnePlusOne).
     r := suite run.
     ^ r passCount")
  1)

(st-test "passing test has no failures"
  (evp
    "| suite r |
     suite := PassingCase suiteForAll: #(#testOnePlusOne).
     r := suite run.
     ^ r failureCount")
  0)

;; ── 3. A subclass with a failing assert: increments failures ──
(smalltalk-load
  "TestCase subclass: #FailingCase
     instanceVariableNames: ''!

   !FailingCase methodsFor: 'tests'!
   testFalse self assert: false!
   testEquals self assert: 1 + 1 equals: 3! !")

(st-test "assert: false bumps failureCount"
  (evp
    "| suite r |
     suite := FailingCase suiteForAll: #(#testFalse).
     r := suite run.
     ^ r failureCount")
  1)

(st-test "assert:equals: with mismatch fails"
  (evp
    "| suite r |
     suite := FailingCase suiteForAll: #(#testEquals).
     r := suite run.
     ^ r failureCount")
  1)

(st-test "failure messageText captured"
  (evp
    "| suite r rec |
     suite := FailingCase suiteForAll: #(#testEquals).
     r := suite run.
     rec := r failures at: 1.
     ^ rec at: 2")
  "expected 3 but got 2")

;; ── 4. Mixed pass/fail counts add up ──
(smalltalk-load
  "TestCase subclass: #MixedCase
     instanceVariableNames: ''!

   !MixedCase methodsFor: 'tests'!
   testGood self assert: true!
   testBad self assert: false!
   testAlsoGood self assert: 2 > 1! !")

(st-test "mixed suite — totalCount"
  (evp
    "| s r |
     s := MixedCase suiteForAll: #(#testGood #testBad #testAlsoGood).
     r := s run.
     ^ r totalCount")
  3)

(st-test "mixed suite — passCount"
  (evp
    "| s r |
     s := MixedCase suiteForAll: #(#testGood #testBad #testAlsoGood).
     r := s run.
     ^ r passCount")
  2)

(st-test "mixed suite — failureCount"
  (evp
    "| s r |
     s := MixedCase suiteForAll: #(#testGood #testBad #testAlsoGood).
     r := s run.
     ^ r failureCount")
  1)

(st-test "allPassed false on mix"
  (evp
    "| s r |
     s := MixedCase suiteForAll: #(#testGood #testBad #testAlsoGood).
     r := s run.
     ^ r allPassed")
  false)

(st-test "allPassed true with only passes"
  (evp
    "| s r |
     s := MixedCase suiteForAll: #(#testGood #testAlsoGood).
     r := s run.
     ^ r allPassed")
  true)

;; ── 5. setUp / tearDown ──
(smalltalk-load
  "TestCase subclass: #FixtureCase
     instanceVariableNames: 'value'!

   !FixtureCase methodsFor: 'fixture'!
   setUp value := 42. ^ self!
   tearDown ^ self! !

   !FixtureCase methodsFor: 'tests'!
   testValueIs42 self assert: value = 42! !")

(st-test "setUp ran before test"
  (evp
    "| s r |
     s := FixtureCase suiteForAll: #(#testValueIs42).
     r := s run.
     ^ r passCount")
  1)

;; ── 6. should:raise: and shouldnt:raise: ──
(smalltalk-load
  "TestCase subclass: #RaiseCase
     instanceVariableNames: ''!

   !RaiseCase methodsFor: 'tests'!
   testShouldRaise
     self should: [Error signal: 'boom'] raise: Error!

   testShouldRaiseFails
     self should: [42] raise: Error!

   testShouldntRaise
     self shouldnt: [42] raise: Error! !")

(st-test "should:raise: catches matching"
  (evp
    "| r |
     r := (RaiseCase suiteForAll: #(#testShouldRaise)) run.
     ^ r passCount") 1)

(st-test "should:raise: fails when no exception"
  (evp
    "| r |
     r := (RaiseCase suiteForAll: #(#testShouldRaiseFails)) run.
     ^ r failureCount") 1)

(st-test "shouldnt:raise: passes when nothing thrown"
  (evp
    "| r |
     r := (RaiseCase suiteForAll: #(#testShouldntRaise)) run.
     ^ r passCount") 1)

;; ── 7. summary string uses format: ──
(st-test "summary contains pass count"
  (let
    ((s (evp
          "| s r |
           s := MixedCase suiteForAll: #(#testGood #testBad).
           r := s run.
           ^ r summary")))
    (cond
      ((not (string? s)) false)
      (else (> (len s) 0))))
  true)

(list st-test-pass st-test-fail)
