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

hybridsjs / hybrids / 19760852732

28 Nov 2025 10:18AM UTC coverage: 42.528% (-57.4%) from 99.915%
19760852732

Pull #296

github

web-flow
Merge 191660095 into 92ed729fc
Pull Request #296: fix(store): improve `store.record()` support & error messages while defining the model

819 of 1842 branches covered (44.46%)

9 of 9 new or added lines in 1 file covered. (100.0%)

1345 existing lines in 27 files now uncovered.

996 of 2342 relevant lines covered (42.53%)

76.9 hits per line

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

80.81
/src/define.js
1
import * as cache from "./cache.js";
2
import * as emitter from "./emitter.js";
3
import { deferred, camelToDash, walkInShadow } from "./utils.js";
4

5
import render from "./render.js";
6
import value from "./value.js";
7

8
export const constructors = new WeakMap();
2✔
9

10
const callbacks = new WeakMap();
2✔
11
const disconnects = new WeakMap();
2✔
12

13
function connectedCallback(host, set) {
14
  for (const fn of this.connects) set.add(fn(host));
61✔
15
  for (const fn of this.observers) set.add(fn(host));
34✔
16
}
17

18
function compile(hybrids, HybridsElement) {
19
  if (HybridsElement) {
38✔
20
    const prevHybrids = constructors.get(HybridsElement);
33✔
21
    if (hybrids === prevHybrids) return HybridsElement;
33!
22

23
    for (const key of Object.keys(prevHybrids)) {
33✔
24
      if (key === "tag") continue;
263✔
25
      delete HybridsElement.prototype[key];
230✔
26
    }
27
  } else {
28
    HybridsElement = class extends globalThis.HTMLElement {
5✔
29
      constructor() {
30
        super();
41✔
31

32
        for (const key of HybridsElement.writable) {
41✔
33
          if (hasOwnProperty.call(this, key)) {
140!
UNCOV
34
            const value = this[key];
×
UNCOV
35
            delete this[key];
×
UNCOV
36
            this[key] = value;
×
37
          } else {
38
            const value = this.getAttribute(camelToDash(key));
140✔
39

40
            if (value !== null) {
140✔
41
              this[key] =
1✔
42
                (value === "" && typeof this[key] === "boolean") || value;
2!
43
            }
44
          }
45
        }
46
      }
47

48
      connectedCallback() {
49
        const set = new Set();
44✔
50
        disconnects.set(this, set);
44✔
51

52
        const cb = connectedCallback.bind(HybridsElement, this, set);
44✔
53
        callbacks.set(this, cb);
44✔
54
        emitter.add(cb);
44✔
55
      }
56

57
      disconnectedCallback() {
58
        emitter.clear(callbacks.get(this));
43✔
59

60
        for (const fn of disconnects.get(this)) {
43✔
61
          if (fn) fn();
61!
62
        }
63

64
        cache.invalidateAll(this);
43✔
65
      }
66
    };
67
  }
68

69
  constructors.set(HybridsElement, Object.freeze(hybrids));
38✔
70

71
  const connects = new Set();
38✔
72
  const observers = new Set();
38✔
73
  const writable = new Set();
38✔
74

75
  for (const key of Object.keys(hybrids)) {
38✔
76
    if (key === "tag") continue;
284✔
77

78
    let desc = hybrids[key];
246✔
79

80
    if (typeof desc !== "object" || desc === null) {
246✔
81
      desc = { value: desc };
31✔
82
    } else if (!hasOwnProperty.call(desc, "value")) {
215!
UNCOV
83
      throw TypeError(
×
84
        `The 'value' option is required for '${key}' property of the '${hybrids.tag}' element`,
85
      );
86
    }
87

88
    desc = key === "render" ? render(desc) : value(key, desc);
246!
89

90
    if (desc.writable) {
246✔
91
      writable.add(key);
128✔
92
    }
93

94
    Object.defineProperty(HybridsElement.prototype, key, {
246✔
95
      get: function get() {
96
        return cache.get(this, key, desc.value);
186✔
97
      },
98
      set: desc.writable
246✔
99
        ? function assert(newValue) {
100
            cache.assert(this, key, newValue);
19✔
101
          }
102
        : undefined,
103
      enumerable: true,
104
      configurable: true,
105
    });
106

107
    if (desc.connect) {
246✔
108
      connects.add((host) =>
59✔
109
        desc.connect(host, key, () => {
61✔
UNCOV
110
          cache.invalidate(host, key);
×
111
        }),
112
      );
113
    }
114

115
    if (desc.observe) {
246!
UNCOV
116
      observers.add((host) =>
×
UNCOV
117
        cache.observe(host, key, desc.value, desc.observe),
×
118
      );
119
    }
120
  }
121

122
  HybridsElement.connects = connects;
38✔
123
  HybridsElement.observers = observers;
38✔
124
  HybridsElement.writable = writable;
38✔
125

126
  return HybridsElement;
38✔
127
}
128

129
const updateQueue = new Map();
2✔
130
function update(HybridsElement) {
131
  if (!updateQueue.size) {
33!
132
    deferred.then(() => {
33✔
133
      walkInShadow(globalThis.document.body, (node) => {
33✔
134
        if (updateQueue.has(node.constructor)) {
1,691✔
135
          const prevHybrids = updateQueue.get(node.constructor);
4✔
136
          const hybrids = constructors.get(node.constructor);
4✔
137
          node.disconnectedCallback();
4✔
138

139
          for (const key of Object.keys(hybrids)) {
4✔
140
            const type = typeof hybrids[key];
14✔
141
            const clearValue =
142
              type !== "object" &&
14✔
143
              type !== "function" &&
144
              hybrids[key] !== prevHybrids[key];
145

146
            if (clearValue) node.removeAttribute(camelToDash(key));
14!
147
            cache.invalidate(node, key, { clearValue });
14✔
148
          }
149

150
          node.connectedCallback();
4✔
151
        }
152
      });
153
      updateQueue.clear();
33✔
154
    });
155
  }
156
  updateQueue.set(HybridsElement, constructors.get(HybridsElement));
33✔
157
}
158

159
const tags = new Set();
2✔
160
function define(hybrids) {
161
  if (!hybrids.tag) {
38!
UNCOV
162
    throw TypeError(
×
163
      "Error while defining an element: 'tag' property with dashed tag name is required",
164
    );
165
  }
166

167
  if (tags.has(hybrids.tag)) {
38!
UNCOV
168
    throw TypeError(
×
169
      `Error while defining '${hybrids.tag}' element: tag name is already defined`,
170
    );
171
  }
172

173
  if (!tags.size) deferred.then(() => tags.clear());
38✔
174
  tags.add(hybrids.tag);
38✔
175

176
  const HybridsElement = globalThis.customElements.get(hybrids.tag);
38✔
177

178
  if (HybridsElement) {
38✔
179
    if (constructors.get(HybridsElement)) {
33!
180
      update(HybridsElement);
33✔
181
      compile(hybrids, HybridsElement);
33✔
182

183
      return hybrids;
33✔
184
    }
185

UNCOV
186
    throw TypeError(
×
187
      `Custom element with '${hybrids.tag}' tag name already defined outside of the hybrids context`,
188
    );
189
  }
190

191
  globalThis.customElements.define(hybrids.tag, compile(hybrids));
5✔
192

193
  return hybrids;
5✔
194
}
195

196
function from(components, { root = "", prefix } = {}) {
×
UNCOV
197
  for (const key of Object.keys(components)) {
×
UNCOV
198
    const hybrids = components[key];
×
199

UNCOV
200
    if (!hybrids.tag) {
×
UNCOV
201
      const tag = camelToDash(
×
202
        []
203
          .concat(root)
UNCOV
204
          .reduce((acc, root) => acc.replace(root, ""), key)
×
205
          .replace(/^[./]+/, "")
206
          .replace(/\//g, "-")
207
          .replace(/\.[a-zA-Z]+$/, ""),
208
      );
209

UNCOV
210
      hybrids.tag = prefix ? `${prefix}-${tag}` : tag;
×
211
    }
212

UNCOV
213
    define(hybrids);
×
214
  }
215

UNCOV
216
  return components;
×
217
}
218

219
export default Object.freeze(
220
  Object.assign(define, {
UNCOV
221
    compile: (hybrids) => compile(hybrids),
×
222
    from,
223
  }),
224
);
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

© 2026 Coveralls, Inc