postwomanTesting.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. export const PASS = "PASS"
  2. export const FAIL = "FAIL"
  3. export const ERROR = "ERROR"
  4. const styles = {
  5. [PASS]: { icon: "check", class: "success-response" },
  6. [FAIL]: { icon: "close", class: "cl-error-response" },
  7. [ERROR]: { icon: "close", class: "cl-error-response" },
  8. none: { icon: "", class: "" },
  9. }
  10. // TODO: probably have to use a more global state for `test`
  11. export default function runTestScriptWithVariables(script, variables) {
  12. let pw = {
  13. _errors: [],
  14. _testReports: [],
  15. _report: "",
  16. expect(value) {
  17. try {
  18. return expect(value, this._testReports)
  19. } catch (e) {
  20. pw._testReports.push({ result: ERROR, message: e })
  21. }
  22. },
  23. test: (descriptor, func) => test(descriptor, func, pw._testReports),
  24. // globals that the script is allowed to have access to.
  25. }
  26. Object.assign(pw, variables)
  27. // run pre-request script within this function so that it has access to the pw object.
  28. new Function("pw", script)(pw)
  29. //
  30. const testReports = pw._testReports.map((item) => {
  31. if (item.result) {
  32. item.styles = styles[item.result]
  33. } else {
  34. item.styles = styles.none
  35. }
  36. return item
  37. })
  38. return { report: pw._report, errors: pw._errors, testResults: testReports }
  39. }
  40. function test(descriptor, func, _testReports) {
  41. _testReports.push({ startBlock: descriptor })
  42. try {
  43. func()
  44. } catch (e) {
  45. _testReports.push({ result: ERROR, message: e })
  46. }
  47. _testReports.push({ endBlock: true })
  48. // TODO: Organize and generate text report of each {descriptor: true} section in testReports.
  49. // add checkmark or x depending on if each testReport is pass=true or pass=false
  50. }
  51. function expect(expectValue, _testReports) {
  52. return new Expectation(expectValue, null, _testReports)
  53. }
  54. class Expectation {
  55. constructor(expectValue, _not, _testReports) {
  56. this.expectValue = expectValue
  57. this.not = _not || new Expectation(this.expectValue, true, _testReports)
  58. this._testReports = _testReports // this values is used within Test.it, which wraps Expectation and passes _testReports value.
  59. this._satisfies = function (expectValue, targetValue) {
  60. // Used for testing if two values match the expectation, which could be === OR !==, depending on if not
  61. // was used. Expectation#_satisfies prevents the need to have an if(this.not) branch in every test method.
  62. // Signature is _satisfies([expectValue,] targetValue): if only one argument is given, it is assumed the targetValue, and expectValue is set to this.expectValue
  63. if (!targetValue) {
  64. targetValue = expectValue
  65. expectValue = this.expectValue
  66. }
  67. if (this.not === true) {
  68. // test the inverse. this.not is always truthly, but an Expectation that is inverted will always be strictly `true`
  69. return expectValue !== targetValue
  70. } else {
  71. return expectValue === targetValue
  72. }
  73. }
  74. }
  75. _fmtNot(message) {
  76. // given a string with "(not)" in it, replaces with "not" or "", depending if the expectation is expecting the positive or inverse (this._not)
  77. if (this.not === true) {
  78. return message.replace("(not)", "not ")
  79. } else {
  80. return message.replace("(not)", "")
  81. }
  82. }
  83. _fail(message) {
  84. return this._testReports.push({ result: FAIL, message })
  85. }
  86. _pass() {
  87. return this._testReports.push({ result: PASS })
  88. }
  89. // TEST METHODS DEFINED BELOW
  90. // these are the usual methods that would follow expect(...)
  91. toBe(value) {
  92. return this._satisfies(value)
  93. ? this._pass()
  94. : this._fail(this._fmtNot(`Expected ${this.expectValue} (not)to be ${value}`))
  95. }
  96. toHaveProperty(value) {
  97. return this._satisfies(Object.prototype.hasOwnProperty.call(this.expectValue, value), true)
  98. ? this._pass()
  99. : this._fail(
  100. this._fmtNot(`Expected object ${this.expectValue} to (not)have property ${value}`)
  101. )
  102. }
  103. toBeLevel2xx() {
  104. const code = parseInt(this.expectValue, 10)
  105. if (Number.isNaN(code)) {
  106. return this._fail(`Expected 200-level status but could not parse value ${this.expectValue}`)
  107. }
  108. return this._satisfies(code >= 200 && code < 300, true)
  109. ? this._pass()
  110. : this._fail(this._fmtNot(`Expected ${this.expectValue} to (not)be 200-level status`))
  111. }
  112. toBeLevel3xx() {
  113. const code = parseInt(this.expectValue, 10)
  114. if (Number.isNaN(code)) {
  115. return this._fail(`Expected 300-level status but could not parse value ${this.expectValue}`)
  116. }
  117. return this._satisfies(code >= 300 && code < 400, true)
  118. ? this._pass()
  119. : this._fail(this._fmtNot(`Expected ${this.expectValue} to (not)be 300-level status`))
  120. }
  121. toBeLevel4xx() {
  122. const code = parseInt(this.expectValue, 10)
  123. if (Number.isNaN(code)) {
  124. return this._fail(`Expected 400-level status but could not parse value ${this.expectValue}`)
  125. }
  126. return this._satisfies(code >= 400 && code < 500, true)
  127. ? this._pass()
  128. : this._fail(this._fmtNot(`Expected ${this.expectValue} to (not)be 400-level status`))
  129. }
  130. toBeLevel5xx() {
  131. const code = parseInt(this.expectValue, 10)
  132. if (Number.isNaN(code)) {
  133. return this._fail(`Expected 500-level status but could not parse value ${this.expectValue}`)
  134. }
  135. return this._satisfies(code >= 500 && code < 600, true)
  136. ? this._pass()
  137. : this._fail(this._fmtNot(`Expected ${this.expectValue} to (not)be 500-level status`))
  138. }
  139. toHaveLength(expectedLength) {
  140. const actualLength = this.expectValue.length
  141. return this._satisfies(actualLength, expectedLength)
  142. ? this._pass()
  143. : this._fail(
  144. this._fmtNot(
  145. `Expected length to be ${expectedLength} but actual length was ${actualLength}`
  146. )
  147. )
  148. }
  149. toBeType(expectedType) {
  150. const actualType = typeof this.expectValue
  151. if (
  152. ![
  153. "string",
  154. "boolean",
  155. "number",
  156. "object",
  157. "undefined",
  158. "bigint",
  159. "symbol",
  160. "function",
  161. ].includes(expectedType)
  162. ) {
  163. return this._fail(
  164. this._fmtNot(
  165. `Argument for toBeType should be "string", "boolean", "number", "object", "undefined", "bigint", "symbol" or "function"`
  166. )
  167. )
  168. }
  169. return this._satisfies(actualType, expectedType)
  170. ? this._pass()
  171. : this._fail(
  172. this._fmtNot(`Expected type to be "${expectedType}" but actual type was "${actualType}"`)
  173. )
  174. }
  175. }