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

hybridsjs / hybrids / 9385835129

05 Jun 2024 02:19PM UTC coverage: 46.684% (-53.2%) from 99.911%
9385835129

Pull #258

github

web-flow
Merge 67d5f8f47 into 2b0c01bf9
Pull Request #258: feat: remove `content` property & add shadow mode detection to render property

611 of 1778 branches covered (34.36%)

54 of 78 new or added lines in 8 files covered. (69.23%)

1172 existing lines in 24 files now uncovered.

1049 of 2247 relevant lines covered (46.68%)

32.63 hits per line

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

73.74
/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));
127✔
15
  for (const fn of this.observers) set.add(fn(host));
104✔
16
}
17

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

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

32
        for (const key of HybridsElement.writable) {
95✔
33
          if (hasOwnProperty.call(this, key)) {
110!
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));
110✔
39

40
            if (value !== null) {
110!
UNCOV
41
              this[key] =
×
42
                (value === "" && typeof this[key] === "boolean") || value;
×
43
            }
44
          }
45
        }
46
      }
47

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

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

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

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

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

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

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

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

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

80
    if (typeof desc !== "object" || desc === null) {
63✔
81
      desc = { value: desc };
42✔
82
    } else if (!hasOwnProperty.call(desc, "value")) {
21!
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);
63✔
89

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

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

107
    if (desc.connect) {
63✔
108
      connects.add((host) =>
20✔
109
        desc.connect(host, key, () => {
49✔
110
          cache.invalidate(host, key);
88✔
111
        }),
112
      );
113
    }
114

115
    if (desc.observe) {
63✔
116
      observers.add((host) =>
26✔
117
        cache.observe(host, key, desc.value, desc.observe),
97✔
118
      );
119
    }
120
  }
121

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

126
  return HybridsElement;
58✔
127
}
128

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

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

UNCOV
146
            if (clearValue) node.removeAttribute(camelToDash(key));
×
UNCOV
147
            cache.invalidate(node, key, { clearValue });
×
148
          }
149

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

159
const tags = new Set();
2✔
160
function define(hybrids) {
161
  if (!hybrids.tag) {
58!
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)) {
58!
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());
58✔
174
  tags.add(hybrids.tag);
58✔
175

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

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

183
      return hybrids;
16✔
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));
42✔
192

193
  return hybrids;
42✔
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