mock.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import * as fs from 'fs/promises';
  2. import * as node from '../source/node.js';
  3. import * as path from 'path';
  4. import * as url from 'url';
  5. import * as worker_threads from 'worker_threads';
  6. const mock = {};
  7. mock.Context = class {
  8. constructor(host, folder, identifier, stream, entries) {
  9. this._host = host;
  10. this._folder = folder;
  11. this._identifier = identifier;
  12. this._stream = stream;
  13. this._entries = entries;
  14. }
  15. get identifier() {
  16. return this._identifier;
  17. }
  18. get stream() {
  19. return this._stream;
  20. }
  21. get entries() {
  22. return this._entries;
  23. }
  24. async request(file, encoding, base) {
  25. return this._host.request(file, encoding, base === undefined ? this._folder : base);
  26. }
  27. async require(id) {
  28. return this._host.require(id);
  29. }
  30. error(error, fatal) {
  31. this._host.exception(error, fatal);
  32. }
  33. };
  34. class CSSStyleDeclaration {
  35. constructor() {
  36. this._properties = new Map();
  37. }
  38. setProperty(name, value) {
  39. this._properties.set(name, value);
  40. }
  41. removeProperty(name) {
  42. this._properties.delete(name);
  43. }
  44. }
  45. class DOMTokenList {
  46. constructor(element) {
  47. this._element = element;
  48. }
  49. add(...tokens) {
  50. const value = this._element.getAttribute('class') || '';
  51. const set = new Set(value.split(' ').concat(...tokens));
  52. this._element.setAttribute('class', Array.from(set).filter((s) => s).join(' '));
  53. }
  54. contains(token) {
  55. const value = this._element.getAttribute('class');
  56. if (value === null || value.indexOf(token) === -1) {
  57. return false;
  58. }
  59. return value.split(' ').some((s) => s === token);
  60. }
  61. }
  62. class HTMLElement {
  63. constructor() {
  64. this._childNodes = [];
  65. this._attributes = new Map();
  66. this._style = new CSSStyleDeclaration();
  67. }
  68. get style() {
  69. return this._style;
  70. }
  71. get childNodes() {
  72. return this._childNodes;
  73. }
  74. get firstChild() {
  75. return this._childNodes.length > 0 ? this._childNodes[0] : null;
  76. }
  77. get lastChild() {
  78. const index = this._childNodes.length - 1;
  79. if (index >= 0) {
  80. return this._childNodes[index];
  81. }
  82. return null;
  83. }
  84. appendChild(node) {
  85. this._childNodes.push(node);
  86. }
  87. insertBefore(newNode, referenceNode) {
  88. const index = this._childNodes.indexOf(referenceNode);
  89. if (index !== -1) {
  90. this._childNodes.splice(index, 0, newNode);
  91. }
  92. }
  93. removeChild(node) {
  94. const index = this._childNodes.lastIndexOf(node);
  95. if (index !== -1) {
  96. this._childNodes.splice(index, 1);
  97. }
  98. }
  99. setAttribute(name, value) {
  100. this._attributes.set(name, value);
  101. }
  102. hasAttribute(name) {
  103. return this._attributes.has(name);
  104. }
  105. getAttribute(name) {
  106. return this._attributes.has(name) ? this._attributes.get(name) : null;
  107. }
  108. getElementsByClassName(name) {
  109. const elements = [];
  110. for (const node of this._childNodes) {
  111. if (node instanceof HTMLElement) {
  112. elements.push(...node.getElementsByClassName(name));
  113. if (node.classList.contains(name)) {
  114. elements.push(node);
  115. }
  116. }
  117. }
  118. return elements;
  119. }
  120. addEventListener(/* event, callback */) {
  121. }
  122. removeEventListener(/* event, callback */) {
  123. }
  124. get classList() {
  125. this._classList = this._classList || new DOMTokenList(this);
  126. return this._classList;
  127. }
  128. getBBox() {
  129. return { x: 0, y: 0, width: 10, height: 10 };
  130. }
  131. getBoundingClientRect() {
  132. return { left: 0, top: 0, width: 0, height: 0 };
  133. }
  134. scrollTo() {
  135. }
  136. focus() {
  137. }
  138. }
  139. class Document {
  140. constructor() {
  141. this._elements = {};
  142. this._documentElement = new HTMLElement();
  143. this._body = new HTMLElement();
  144. }
  145. get documentElement() {
  146. return this._documentElement;
  147. }
  148. get body() {
  149. return this._body;
  150. }
  151. createElement(/* name */) {
  152. return new HTMLElement();
  153. }
  154. createElementNS(/* namespace, name */) {
  155. return new HTMLElement();
  156. }
  157. createTextNode(/* text */) {
  158. return new HTMLElement();
  159. }
  160. getElementById(id) {
  161. let element = this._elements[id];
  162. if (!element) {
  163. element = new HTMLElement();
  164. this._elements[id] = element;
  165. }
  166. return element;
  167. }
  168. addEventListener(/* event, callback */) {
  169. }
  170. removeEventListener(/* event, callback */) {
  171. }
  172. }
  173. class Window {
  174. constructor() {
  175. this._document = new Document();
  176. }
  177. get document() {
  178. return this._document;
  179. }
  180. addEventListener(/* event, callback */) {
  181. }
  182. removeEventListener(/* event, callback */) {
  183. }
  184. requestAnimationFrame(callback) {
  185. callback();
  186. }
  187. }
  188. mock.Host = class {
  189. constructor(environment) {
  190. this._environment = environment;
  191. this._errors = [];
  192. mock.Host.source = mock.Host.source || this._dirname('..', 'source');
  193. mock.Host.window = mock.Host.window || new Window();
  194. }
  195. async view(/* view */) {
  196. }
  197. async start() {
  198. }
  199. get window() {
  200. return mock.Host.window;
  201. }
  202. get document() {
  203. return this.window.document;
  204. }
  205. get errors() {
  206. return this._errors;
  207. }
  208. get type() {
  209. return 'Test';
  210. }
  211. environment(name) {
  212. return this._environment[name];
  213. }
  214. update() {
  215. }
  216. screen(/* name */) {
  217. }
  218. async require(id) {
  219. const file = path.join(mock.Host.source, `${id}.js`);
  220. return await import(`file://${file}`);
  221. }
  222. worker(id) {
  223. const file = path.join(mock.Host.source, `${id}.js`);
  224. const worker = new worker_threads.Worker(file);
  225. worker.addEventListener = (type, listener) => {
  226. worker.on(type, (message) => listener({ data: message }));
  227. };
  228. return worker;
  229. }
  230. async request(file, encoding, basename) {
  231. const pathname = path.join(basename || mock.Host.source, file);
  232. const exists = await this._access(pathname);
  233. if (!exists) {
  234. throw new Error(`The file '${file}' does not exist.`);
  235. }
  236. const stats = await fs.stat(pathname);
  237. if (stats.isDirectory()) {
  238. throw new Error(`The path '${file}' is a directory.`);
  239. }
  240. if (encoding) {
  241. return await fs.readFile(pathname, encoding);
  242. }
  243. return new node.FileStream(pathname, 0, stats.size, stats.mtimeMs);
  244. }
  245. event(/* name, params */) {
  246. }
  247. exception(error /*, fatal */) {
  248. this._errors.push(error);
  249. }
  250. message() {
  251. }
  252. async _access(path) {
  253. try {
  254. await fs.access(path);
  255. return true;
  256. } catch {
  257. return false;
  258. }
  259. }
  260. _dirname(...args) {
  261. const file = url.fileURLToPath(import.meta.url);
  262. const dir = path.dirname(file);
  263. return path.join(dir, ...args);
  264. }
  265. };
  266. export const Host = mock.Host;
  267. export const Context = mock.Context;