- Fixed registration form not displaying due to missing HVAC_Security_Helpers dependency - Added require_once for dependencies in class-hvac-shortcodes.php render_registration() - Fixed event edit HTTP 500 error by correcting class instantiation to HVAC_Event_Manager - Created comprehensive E2E test suite with MCP Playwright integration - Achieved 70% test success rate with both issues fully resolved 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
397 lines
No EOL
10 KiB
JavaScript
397 lines
No EOL
10 KiB
JavaScript
/**
|
|
* Feature Detection System
|
|
* Detects browser capabilities instead of relying on user agent strings
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
var HVACFeatureDetection = (function() {
|
|
'use strict';
|
|
|
|
var features = {};
|
|
var _hasRunDetection = false;
|
|
|
|
/**
|
|
* Detect all features
|
|
*/
|
|
function detectAll() {
|
|
if (_hasRunDetection) {
|
|
return features;
|
|
}
|
|
|
|
console.log('[Feature Detection] Running capability tests...');
|
|
|
|
features = {
|
|
// Storage capabilities
|
|
localStorage: testLocalStorage(),
|
|
sessionStorage: testSessionStorage(),
|
|
cookies: navigator.cookieEnabled || false,
|
|
indexedDB: testIndexedDB(),
|
|
|
|
// JavaScript features
|
|
es6: testES6Support(),
|
|
promises: typeof Promise !== 'undefined',
|
|
fetch: typeof fetch !== 'undefined',
|
|
async: testAsyncSupport(),
|
|
|
|
// DOM features
|
|
mutationObserver: 'MutationObserver' in window,
|
|
intersectionObserver: 'IntersectionObserver' in window,
|
|
resizeObserver: 'ResizeObserver' in window,
|
|
|
|
// CSS features
|
|
cssGrid: testCSSSupport('display', 'grid'),
|
|
cssFlexbox: testCSSSupport('display', 'flex'),
|
|
cssVariables: testCSSVariables(),
|
|
cssTransforms: testCSSSupport('transform', 'translateX(1px)'),
|
|
cssTransitions: testCSSSupport('transition', 'all 0.3s'),
|
|
cssFilters: testCSSSupport('filter', 'blur(1px)'),
|
|
|
|
// Media features
|
|
webGL: testWebGL(),
|
|
canvas: testCanvas(),
|
|
svg: testSVG(),
|
|
webAudio: 'AudioContext' in window || 'webkitAudioContext' in window,
|
|
|
|
// Network features
|
|
serviceWorker: 'serviceWorker' in navigator,
|
|
webSockets: 'WebSocket' in window,
|
|
webRTC: testWebRTC(),
|
|
|
|
// Input features
|
|
touch: testTouch(),
|
|
pointer: 'PointerEvent' in window,
|
|
|
|
// Performance features
|
|
performanceAPI: 'performance' in window,
|
|
navigationTiming: !!(window.performance && window.performance.timing),
|
|
|
|
// Safari-specific issues
|
|
safariPrivateBrowsing: testSafariPrivateBrowsing(),
|
|
safariITP: testSafariITP()
|
|
};
|
|
|
|
_hasRunDetection = true;
|
|
|
|
console.log('[Feature Detection] Capabilities detected:', features);
|
|
|
|
return features;
|
|
}
|
|
|
|
/**
|
|
* Test localStorage availability
|
|
*/
|
|
function testLocalStorage() {
|
|
try {
|
|
var test = '__test__';
|
|
localStorage.setItem(test, test);
|
|
localStorage.removeItem(test);
|
|
return true;
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test sessionStorage availability
|
|
*/
|
|
function testSessionStorage() {
|
|
try {
|
|
var test = '__test__';
|
|
sessionStorage.setItem(test, test);
|
|
sessionStorage.removeItem(test);
|
|
return true;
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test IndexedDB availability
|
|
*/
|
|
function testIndexedDB() {
|
|
try {
|
|
return !!(window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB);
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test ES6 support
|
|
*/
|
|
function testES6Support() {
|
|
try {
|
|
// Test arrow functions
|
|
eval('(() => {})');
|
|
// Test template literals
|
|
eval('`test`');
|
|
// Test let/const
|
|
eval('let a = 1; const b = 2;');
|
|
// Test destructuring
|
|
eval('const {a, b} = {a: 1, b: 2}');
|
|
return true;
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test async/await support
|
|
*/
|
|
function testAsyncSupport() {
|
|
try {
|
|
eval('(async function() {})');
|
|
return true;
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test CSS support
|
|
*/
|
|
function testCSSSupport(property, value) {
|
|
// Use CSS.supports if available
|
|
if (window.CSS && window.CSS.supports) {
|
|
return CSS.supports(property, value);
|
|
}
|
|
|
|
// Fallback method
|
|
var el = document.createElement('div');
|
|
el.style.cssText = property + ':' + value;
|
|
return el.style[property] !== '';
|
|
}
|
|
|
|
/**
|
|
* Test CSS variables support
|
|
*/
|
|
function testCSSVariables() {
|
|
return testCSSSupport('--test', '1px');
|
|
}
|
|
|
|
/**
|
|
* Test WebGL support
|
|
*/
|
|
function testWebGL() {
|
|
try {
|
|
var canvas = document.createElement('canvas');
|
|
return !!(window.WebGLRenderingContext &&
|
|
(canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Canvas support
|
|
*/
|
|
function testCanvas() {
|
|
var elem = document.createElement('canvas');
|
|
return !!(elem.getContext && elem.getContext('2d'));
|
|
}
|
|
|
|
/**
|
|
* Test SVG support
|
|
*/
|
|
function testSVG() {
|
|
return !!(document.createElementNS && document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect);
|
|
}
|
|
|
|
/**
|
|
* Test WebRTC support
|
|
*/
|
|
function testWebRTC() {
|
|
return !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
|
|
}
|
|
|
|
/**
|
|
* Test touch support
|
|
*/
|
|
function testTouch() {
|
|
return ('ontouchstart' in window) ||
|
|
(navigator.maxTouchPoints > 0) ||
|
|
(navigator.msMaxTouchPoints > 0);
|
|
}
|
|
|
|
/**
|
|
* Test Safari private browsing mode
|
|
*/
|
|
function testSafariPrivateBrowsing() {
|
|
try {
|
|
// Safari private mode throws quota exceeded immediately
|
|
localStorage.setItem('__private_test__', '1');
|
|
localStorage.removeItem('__private_test__');
|
|
return false;
|
|
} catch(e) {
|
|
// Check if it's quota exceeded error
|
|
if (e.code === 22 || e.code === 1014 || e.name === 'QuotaExceededError') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Safari ITP restrictions
|
|
*/
|
|
function testSafariITP() {
|
|
// Check if this looks like Safari
|
|
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
|
|
if (!isSafari) {
|
|
return false;
|
|
}
|
|
|
|
// Safari with ITP has specific storage restrictions
|
|
// This is a heuristic check
|
|
try {
|
|
// Check if third-party cookies are blocked
|
|
var testCookie = '__itp_test__=1;SameSite=None;Secure';
|
|
document.cookie = testCookie;
|
|
var hasITP = document.cookie.indexOf('__itp_test__') === -1;
|
|
|
|
// Clean up
|
|
if (!hasITP) {
|
|
document.cookie = '__itp_test__=;expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
|
}
|
|
|
|
return hasITP;
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get feature support level
|
|
*/
|
|
function getSupportLevel() {
|
|
if (!_hasRunDetection) {
|
|
detectAll();
|
|
}
|
|
|
|
var critical = [
|
|
'localStorage',
|
|
'sessionStorage',
|
|
'cookies',
|
|
'es6',
|
|
'promises',
|
|
'mutationObserver',
|
|
'cssFlexbox'
|
|
];
|
|
|
|
var enhanced = [
|
|
'fetch',
|
|
'async',
|
|
'intersectionObserver',
|
|
'cssGrid',
|
|
'cssVariables'
|
|
];
|
|
|
|
var allCritical = critical.every(function(feature) {
|
|
return features[feature];
|
|
});
|
|
|
|
var allEnhanced = enhanced.every(function(feature) {
|
|
return features[feature];
|
|
});
|
|
|
|
if (!allCritical) {
|
|
return 'minimal';
|
|
} else if (!allEnhanced) {
|
|
return 'basic';
|
|
} else {
|
|
return 'full';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if feature is supported
|
|
*/
|
|
function isSupported(feature) {
|
|
if (!_hasRunDetection) {
|
|
detectAll();
|
|
}
|
|
return !!features[feature];
|
|
}
|
|
|
|
/**
|
|
* Get recommended polyfills
|
|
*/
|
|
function getRecommendedPolyfills() {
|
|
if (!_hasRunDetection) {
|
|
detectAll();
|
|
}
|
|
|
|
var polyfills = [];
|
|
|
|
if (!features.promises) {
|
|
polyfills.push('promise-polyfill');
|
|
}
|
|
|
|
if (!features.fetch) {
|
|
polyfills.push('whatwg-fetch');
|
|
}
|
|
|
|
if (!features.intersectionObserver) {
|
|
polyfills.push('intersection-observer');
|
|
}
|
|
|
|
if (!features.cssVariables) {
|
|
polyfills.push('css-vars-ponyfill');
|
|
}
|
|
|
|
return polyfills;
|
|
}
|
|
|
|
/**
|
|
* Initialize and run detection
|
|
*/
|
|
function init() {
|
|
detectAll();
|
|
|
|
// Add data attributes to body for CSS targeting
|
|
var body = document.body;
|
|
if (body) {
|
|
body.setAttribute('data-feature-level', getSupportLevel());
|
|
|
|
// Add specific feature flags
|
|
if (features.safariPrivateBrowsing) {
|
|
body.setAttribute('data-safari-private', 'true');
|
|
}
|
|
|
|
if (features.safariITP) {
|
|
body.setAttribute('data-safari-itp', 'true');
|
|
}
|
|
|
|
if (!features.es6) {
|
|
body.setAttribute('data-legacy-js', 'true');
|
|
}
|
|
}
|
|
|
|
return features;
|
|
}
|
|
|
|
// Public API
|
|
return {
|
|
init: init,
|
|
detectAll: detectAll,
|
|
isSupported: isSupported,
|
|
getSupportLevel: getSupportLevel,
|
|
getRecommendedPolyfills: getRecommendedPolyfills,
|
|
features: function() { return features; }
|
|
};
|
|
})();
|
|
|
|
// Initialize on DOM ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
HVACFeatureDetection.init();
|
|
});
|
|
} else {
|
|
HVACFeatureDetection.init();
|
|
}
|
|
|
|
// Make globally available
|
|
window.HVACFeatureDetection = HVACFeatureDetection; |