• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / js-slang / 5936042370

22 Aug 2023 07:58AM UTC coverage: 83.014% (+0.1%) from 82.893%
5936042370

Pull #1471

github

web-flow
Merge 6d88e2972 into 1d991166b
Pull Request #1471: Use Asynchronous Code for Loading Modules

3618 of 4779 branches covered (75.71%)

Branch coverage included in aggregate %.

269 of 269 new or added lines in 23 files covered. (100.0%)

10838 of 12635 relevant lines covered (85.78%)

106101.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.89
/src/modules/moduleLoader.ts
1
import es from 'estree'
2
import { memoize } from 'lodash'
66✔
3
import { XMLHttpRequest as NodeXMLHttpRequest } from 'xmlhttprequest-ts'
66✔
4

5
import {
66✔
6
  ModuleConnectionError,
7
  ModuleInternalError,
8
  ModuleNotFoundError
9
} from '../errors/moduleErrors'
10
import { Context } from '../types'
11
import { wrapSourceModule } from '../utils/operators'
66✔
12
import { ModuleBundle, ModuleDocumentation, ModuleFunctions, ModuleManifest } from './moduleTypes'
13
import { getRequireProvider } from './requireProvider'
66✔
14

15
// Supports both JSDom (Web Browser) environment and Node environment
16
export const newHttpRequest = () =>
66✔
17
  typeof window === 'undefined' ? new NodeXMLHttpRequest() : new XMLHttpRequest()
21!
18

19
// Default modules static url. Exported for testing.
20
export let MODULES_STATIC_URL = 'https://source-academy.github.io/modules'
66✔
21

22
export function setModulesStaticURL(url: string) {
66✔
23
  MODULES_STATIC_URL = url
1✔
24
}
25

26
/**
27
 * Send a HTTP Get request to the specified endpoint.
28
 * @return NodeXMLHttpRequest | XMLHttpRequest
29
 */
30
export function httpGet(url: string): string {
66✔
31
  const request = newHttpRequest()
20✔
32
  try {
20✔
33
    // If running function in node environment, set request timeout
34
    if (typeof window === 'undefined') request.timeout = 10000
20!
35
    request.open('GET', url, false)
20✔
36
    request.send(null)
20✔
37
  } catch (error) {
38
    if (!(error instanceof DOMException)) throw error
×
39
  }
40
  if (request.status !== 200 && request.status !== 304) throw new ModuleConnectionError()
20✔
41
  return request.responseText
19✔
42
}
43

44
/**
45
 * Send a HTTP GET request to the modules endpoint to retrieve the manifest
46
 * @return Modules
47
 */
48
export const memoizedGetModuleManifest = memoize(getModuleManifest)
66✔
49
function getModuleManifest(): ModuleManifest {
50
  const rawManifest = httpGet(`${MODULES_STATIC_URL}/modules.json`)
8✔
51
  return JSON.parse(rawManifest)
8✔
52
}
53

54
/**
55
 * Send a HTTP GET request to the modules endpoint to retrieve the specified file
56
 * @return String of module file contents
57
 */
58

59
const memoizedGetModuleFileInternal = memoize(getModuleFile)
66✔
60
export const memoizedGetModuleFile = (name: string, type: 'tab' | 'bundle' | 'json') =>
66✔
61
  memoizedGetModuleFileInternal({ name, type })
10✔
62
function getModuleFile({ name, type }: { name: string; type: 'tab' | 'bundle' | 'json' }): string {
63
  return httpGet(`${MODULES_STATIC_URL}/${type}s/${name}.js${type === 'json' ? 'on' : ''}`)
10!
64
}
65

66
/**
67
 * Loads the respective module package (functions from the module)
68
 * @param path imported module name
69
 * @param context
70
 * @param node import declaration node
71
 * @returns the module's functions object
72
 */
73
export function loadModuleBundle(path: string, context: Context, node?: es.Node): ModuleFunctions {
66✔
74
  const modules = memoizedGetModuleManifest()
4✔
75

76
  // Check if the module exists
77
  const moduleList = Object.keys(modules)
4✔
78
  if (moduleList.includes(path) === false) throw new ModuleNotFoundError(path, node)
4!
79

80
  // Get module file
81
  const moduleText = memoizedGetModuleFile(path, 'bundle')
4✔
82
  try {
4✔
83
    const moduleBundle: ModuleBundle = eval(moduleText)
4✔
84
    return wrapSourceModule(path, moduleBundle, getRequireProvider(context))
3✔
85
  } catch (error) {
86
    // console.error("bundle error: ", error)
87
    throw new ModuleInternalError(path, error, node)
1✔
88
  }
89
}
90

91
/**
92
 * Loads the module contents of a package
93
 *
94
 * @param path imported module name
95
 * @param node import declaration node
96
 * @returns an array of functions
97
 */
98
export function loadModuleTabs(path: string, node?: es.Node) {
66✔
99
  const modules = memoizedGetModuleManifest()
4✔
100
  // Check if the module exists
101
  const moduleList = Object.keys(modules)
4✔
102
  if (moduleList.includes(path) === false) throw new ModuleNotFoundError(path, node)
4!
103

104
  // Retrieves the tabs the module has from modules.json
105
  const sideContentTabPaths: string[] = modules[path].tabs
4✔
106
  // Load the tabs for the current module
107
  return sideContentTabPaths.map(path => {
4✔
108
    const rawTabFile = memoizedGetModuleFile(path, 'tab')
4✔
109
    try {
4✔
110
      return eval(rawTabFile)
4✔
111
    } catch (error) {
112
      // console.error('tab error:', error);
113
      throw new ModuleInternalError(path, error, node)
1✔
114
    }
115
  })
116
}
117

118
export const memoizedloadModuleDocs = memoize(loadModuleDocs)
66✔
119
export function loadModuleDocs(path: string, node?: es.Node) {
66✔
120
  try {
×
121
    const modules = memoizedGetModuleManifest()
×
122
    // Check if the module exists
123
    const moduleList = Object.keys(modules)
×
124
    if (!moduleList.includes(path)) throw new ModuleNotFoundError(path, node)
×
125
    const result = getModuleFile({ name: path, type: 'json' })
×
126
    return JSON.parse(result) as ModuleDocumentation
×
127
  } catch (error) {
128
    console.warn('Failed to load module documentation')
×
129
    return null
×
130
  }
131
}
132

133
export function initModuleContext(
66✔
134
  moduleName: string,
135
  context: Context,
136
  loadTabs: boolean,
137
  node?: es.Node
138
) {
139
  if (!(moduleName in context.moduleContexts)) {
2!
140
    context.moduleContexts[moduleName] = {
2✔
141
      state: null,
142
      tabs: loadTabs ? loadModuleTabs(moduleName, node) : null
2!
143
    }
144
  } else if (context.moduleContexts[moduleName].tabs === null && loadTabs) {
×
145
    context.moduleContexts[moduleName].tabs = loadModuleTabs(moduleName, node)
×
146
  }
147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc