Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26d36213e8 |
@@ -36,23 +36,6 @@ module Admin
|
|||||||
max_scroll: max_scroll_by_path[path].to_i
|
max_scroll: max_scroll_by_path[path].to_i
|
||||||
}
|
}
|
||||||
end.sort_by { |r| [-r[:pageviews], -r[:moves], -r[:clicks], r[:page_path]] }
|
end.sort_by { |r| [-r[:pageviews], -r[:moves], -r[:clicks], r[:page_path]] }
|
||||||
|
|
||||||
pageviews_by_day = scope.group(:day).sum(:pageview_count)
|
|
||||||
clicks_by_day = cell_scope.group(:day).sum(:click_count)
|
|
||||||
moves_by_day = cell_scope.group(:day).sum(:move_count)
|
|
||||||
@trend = (@filters[:from]..@filters[:to]).map do |day|
|
|
||||||
{
|
|
||||||
day: day.iso8601,
|
|
||||||
pageviews: pageviews_by_day[day].to_i,
|
|
||||||
clicks: clicks_by_day[day].to_i,
|
|
||||||
moves: moves_by_day[day].to_i
|
|
||||||
}
|
|
||||||
end
|
|
||||||
@trend_totals = {
|
|
||||||
pageviews: @trend.sum { |r| r[:pageviews] },
|
|
||||||
clicks: @trend.sum { |r| r[:clicks] },
|
|
||||||
moves: @trend.sum { |r| r[:moves] }
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|||||||
@@ -58,40 +58,6 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @trend.any? && (@trend_totals[:pageviews].positive? || @trend_totals[:clicks].positive? || @trend_totals[:moves].positive?) %>
|
|
||||||
<section class="panel admin-analytics-trend">
|
|
||||||
<h3><%= t("admin.analytics.index.trend_title") %></h3>
|
|
||||||
<p class="muted admin-table-sub"><%= t("admin.analytics.index.trend_lead") %></p>
|
|
||||||
<div class="kpi-grid admin-analytics-trend__kpi">
|
|
||||||
<div class="kpi">
|
|
||||||
<div class="kpi-label"><%= t("admin.analytics.index.table.pageviews") %></div>
|
|
||||||
<div class="kpi-value"><%= @trend_totals[:pageviews] %></div>
|
|
||||||
</div>
|
|
||||||
<div class="kpi">
|
|
||||||
<div class="kpi-label"><%= t("admin.analytics.index.table.clicks") %></div>
|
|
||||||
<div class="kpi-value"><%= @trend_totals[:clicks] %></div>
|
|
||||||
</div>
|
|
||||||
<div class="kpi">
|
|
||||||
<div class="kpi-label"><%= t("admin.analytics.index.table.moves") %></div>
|
|
||||||
<div class="kpi-value"><%= @trend_totals[:moves] %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chart-wrap chart-wrap--analytics">
|
|
||||||
<canvas id="chart-analytics-trend" aria-label="<%= t("admin.analytics.index.trend_title") %>"></canvas>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<script>
|
|
||||||
window.adminAnalyticsTrend = <%= raw @trend.to_json %>;
|
|
||||||
window.adminAnalyticsI18n = {
|
|
||||||
pageviews: <%= raw t("admin.analytics.index.table.pageviews").to_json %>,
|
|
||||||
clicks: <%= raw t("admin.analytics.index.table.clicks").to_json %>,
|
|
||||||
moves: <%= raw t("admin.analytics.index.table.moves").to_json %>
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" crossorigin="anonymous"></script>
|
|
||||||
<script src="/admin-analytics.js?v=1" defer></script>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<% if @pages.any? %>
|
<% if @pages.any? %>
|
||||||
<div class="admin-table-wrap">
|
<div class="admin-table-wrap">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
<link rel="stylesheet" href="/admin.css?v=17">
|
<link rel="stylesheet" href="/admin.css?v=16">
|
||||||
<%= yield :head %>
|
<%= yield :head %>
|
||||||
<% if content_for?(:replay_archive_styles) %>
|
<% if content_for?(:replay_archive_styles) %>
|
||||||
<link rel="stylesheet" href="/marketing.css?v=42">
|
<link rel="stylesheet" href="/marketing.css?v=42">
|
||||||
|
|||||||
@@ -427,8 +427,6 @@ de:
|
|||||||
lead: Aggregierte First-Party-Heatmaps (Bewegung/Klick) und Scrolltiefe, nur mit Statistik-Einwilligung. Keine personenbezogenen Daten.
|
lead: Aggregierte First-Party-Heatmaps (Bewegung/Klick) und Scrolltiefe, nur mit Statistik-Einwilligung. Keine personenbezogenen Daten.
|
||||||
none: Keine Daten im gewählten Zeitraum.
|
none: Keine Daten im gewählten Zeitraum.
|
||||||
heatmap: Heatmap
|
heatmap: Heatmap
|
||||||
trend_title: Verlauf über die Zeit
|
|
||||||
trend_lead: Tägliche Pageviews, Klicks und Bewegungen im gefilterten Zeitraum.
|
|
||||||
table:
|
table:
|
||||||
path: Seite
|
path: Seite
|
||||||
pageviews: Pageviews
|
pageviews: Pageviews
|
||||||
|
|||||||
@@ -427,8 +427,6 @@ en:
|
|||||||
lead: Aggregated first-party move/click heatmaps and scroll depth, only with analytics consent. No personal data.
|
lead: Aggregated first-party move/click heatmaps and scroll depth, only with analytics consent. No personal data.
|
||||||
none: No data in the selected period.
|
none: No data in the selected period.
|
||||||
heatmap: Heatmap
|
heatmap: Heatmap
|
||||||
trend_title: Trend over time
|
|
||||||
trend_lead: Daily pageviews, clicks and moves for the filtered period.
|
|
||||||
table:
|
table:
|
||||||
path: Page
|
path: Page
|
||||||
pageviews: Pageviews
|
pageviews: Pageviews
|
||||||
|
|||||||
@@ -427,8 +427,6 @@ es:
|
|||||||
lead: Heatmaps de movimientos/clics y scroll agregados (first-party), solo con consentimiento estadístico. Sin datos personales.
|
lead: Heatmaps de movimientos/clics y scroll agregados (first-party), solo con consentimiento estadístico. Sin datos personales.
|
||||||
none: No hay datos en el periodo seleccionado.
|
none: No hay datos en el periodo seleccionado.
|
||||||
heatmap: Heatmap
|
heatmap: Heatmap
|
||||||
trend_title: Evolución en el tiempo
|
|
||||||
trend_lead: Pageviews, clics y movimientos día a día en el periodo filtrado.
|
|
||||||
table:
|
table:
|
||||||
path: Página
|
path: Página
|
||||||
pageviews: Pageviews
|
pageviews: Pageviews
|
||||||
|
|||||||
@@ -427,8 +427,6 @@ fr:
|
|||||||
lead: Heatmaps mouvements/clics et scroll agrégés (first-party), uniquement avec consentement statistiques. Aucune donnée personnelle.
|
lead: Heatmaps mouvements/clics et scroll agrégés (first-party), uniquement avec consentement statistiques. Aucune donnée personnelle.
|
||||||
none: Aucune donnée sur la période sélectionnée.
|
none: Aucune donnée sur la période sélectionnée.
|
||||||
heatmap: Heatmap
|
heatmap: Heatmap
|
||||||
trend_title: Évolution dans le temps
|
|
||||||
trend_lead: Pages vues, clics et mouvements par jour sur la période filtrée.
|
|
||||||
table:
|
table:
|
||||||
path: Page
|
path: Page
|
||||||
pageviews: Pages vues
|
pageviews: Pages vues
|
||||||
|
|||||||
@@ -448,8 +448,6 @@ it:
|
|||||||
lead: Heatmap movimenti/click e scroll aggregati (first-party), solo con consenso statistico. Nessun dato personale.
|
lead: Heatmap movimenti/click e scroll aggregati (first-party), solo con consenso statistico. Nessun dato personale.
|
||||||
none: Nessun dato nel periodo selezionato.
|
none: Nessun dato nel periodo selezionato.
|
||||||
heatmap: Heatmap
|
heatmap: Heatmap
|
||||||
trend_title: Andamento nel tempo
|
|
||||||
trend_lead: Pageview, click e movimenti giorno per giorno nel periodo filtrato.
|
|
||||||
table:
|
table:
|
||||||
path: Pagina
|
path: Pagina
|
||||||
pageviews: Pageview
|
pageviews: Pageview
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var trend = window.adminAnalyticsTrend || [];
|
|
||||||
var i18n = window.adminAnalyticsI18n || {};
|
|
||||||
var canvas = document.getElementById("chart-analytics-trend");
|
|
||||||
if (!canvas || !trend.length || typeof Chart === "undefined") return;
|
|
||||||
|
|
||||||
function dayLabel(dayStr) {
|
|
||||||
var parts = dayStr.split("-");
|
|
||||||
if (parts.length < 3) return dayStr;
|
|
||||||
var d = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
|
|
||||||
return d.toLocaleDateString([], { day: "2-digit", month: "short" });
|
|
||||||
}
|
|
||||||
|
|
||||||
var labels = trend.map(function (row) {
|
|
||||||
return dayLabel(row.day);
|
|
||||||
});
|
|
||||||
|
|
||||||
new Chart(canvas, {
|
|
||||||
type: "line",
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: i18n.pageviews || "Pageviews",
|
|
||||||
data: trend.map(function (r) { return r.pageviews || 0; }),
|
|
||||||
borderColor: "rgba(229, 57, 53, 0.95)",
|
|
||||||
backgroundColor: "rgba(229, 57, 53, 0.18)",
|
|
||||||
fill: true,
|
|
||||||
tension: 0.25,
|
|
||||||
pointRadius: 3,
|
|
||||||
pointHoverRadius: 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: i18n.clicks || "Clicks",
|
|
||||||
data: trend.map(function (r) { return r.clicks || 0; }),
|
|
||||||
borderColor: "rgba(66, 165, 245, 0.95)",
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
fill: false,
|
|
||||||
tension: 0.25,
|
|
||||||
pointRadius: 2,
|
|
||||||
pointHoverRadius: 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: i18n.moves || "Moves",
|
|
||||||
data: trend.map(function (r) { return r.moves || 0; }),
|
|
||||||
borderColor: "rgba(255, 193, 7, 0.85)",
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
fill: false,
|
|
||||||
tension: 0.25,
|
|
||||||
pointRadius: 2,
|
|
||||||
pointHoverRadius: 4,
|
|
||||||
borderDash: [4, 3]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
interaction: { mode: "index", intersect: false },
|
|
||||||
animation: { duration: 280 },
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
ticks: { maxTicksLimit: 14, color: "#9a9aad", font: { size: 10 } },
|
|
||||||
grid: { color: "rgba(255,255,255,0.06)" }
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
ticks: {
|
|
||||||
color: "#9a9aad",
|
|
||||||
font: { size: 10 },
|
|
||||||
precision: 0
|
|
||||||
},
|
|
||||||
grid: { color: "rgba(255,255,255,0.06)" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: { labels: { color: "#ccc", boxWidth: 12 } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@@ -173,15 +173,6 @@ body.admin-body {
|
|||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-wrap--analytics {
|
|
||||||
height: 260px;
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-analytics-trend__kpi {
|
|
||||||
margin: 0.75rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-panels {
|
.admin-panels {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.4fr 1fr;
|
grid-template-columns: 1.4fr 1fr;
|
||||||
|
|||||||
@@ -33,25 +33,6 @@ RSpec.describe "Admin analytics", type: :request do
|
|||||||
expect(response.body).to include("/prezzi")
|
expect(response.body).to include("/prezzi")
|
||||||
expect(response.body).to include("12")
|
expect(response.body).to include("12")
|
||||||
expect(response.body).to include("40")
|
expect(response.body).to include("40")
|
||||||
expect(response.body).to include("chart-analytics-trend")
|
|
||||||
expect(response.body).to include("adminAnalyticsTrend")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "mostra il trend anche su più giorni" do
|
|
||||||
AnalyticsPageStat.create!(
|
|
||||||
day: 1.day.ago.to_date,
|
|
||||||
page_path: "/prezzi",
|
|
||||||
device: "desktop",
|
|
||||||
pageview_count: 5,
|
|
||||||
scroll_samples: 0,
|
|
||||||
scroll_sum_pct: 0,
|
|
||||||
max_scroll_pct: 0
|
|
||||||
)
|
|
||||||
|
|
||||||
get admin_analytics_path, params: { from: 2.days.ago.to_date, to: Time.zone.today }
|
|
||||||
expect(response).to have_http_status(:ok)
|
|
||||||
expect(response.body).to include("\"pageviews\":5")
|
|
||||||
expect(response.body).to include("\"pageviews\":12")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "mostra la heatmap di una pagina con tab dispositivo" do
|
it "mostra la heatmap di una pagina con tab dispositivo" do
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ android {
|
|||||||
applicationId = "com.matchlivetv.match_live_tv"
|
applicationId = "com.matchlivetv.match_live_tv"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 34
|
versionCode = 33
|
||||||
versionName = "2.0.13-native"
|
versionName = "2.0.12-native"
|
||||||
|
|
||||||
val apiBaseUrl = project.findProperty("API_BASE_URL") as String?
|
val apiBaseUrl = project.findProperty("API_BASE_URL") as String?
|
||||||
?: "https://www.matchlivetv.it"
|
?: "https://www.matchlivetv.it"
|
||||||
|
|||||||
@@ -677,11 +677,11 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -698,7 +698,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||||
@@ -709,7 +709,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -727,7 +727,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
@@ -739,7 +739,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -756,11 +756,11 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -775,11 +775,11 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.uitests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.uitests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -794,11 +794,11 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.uitests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.uitests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 215 KiB |
@@ -19,7 +19,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.0.12</string>
|
<string>2.0.13</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>35</string>
|
<string>37</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
|
|||||||
@@ -1,7 +1,45 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
@testable import MatchLiveTv
|
||||||
|
|
||||||
/// Smoke test eseguibile senza @testable import (validazione logica pura).
|
/// Logica calendario/API reale + smoke parser (RTMP/overlay) senza dipendenze di rete.
|
||||||
final class ApiInstantTests: XCTestCase {
|
final class ApiInstantTests: XCTestCase {
|
||||||
|
func testParseApiInstantRailsOffsetWithMillis() {
|
||||||
|
let now = iso("2026-06-06T16:00:00Z")
|
||||||
|
XCTAssertTrue(ApiInstant.isScheduledOnCalendar("2026-06-06T20:00:00.000+02:00", now: now))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIsScheduledOnCalendarKeepsThisMorningAfterKickoff() {
|
||||||
|
let kickoff = "2026-09-06T07:00:00Z"
|
||||||
|
let now = iso("2026-09-06T08:30:00Z")
|
||||||
|
XCTAssertTrue(ApiInstant.isScheduledOnCalendar(kickoff, now: now))
|
||||||
|
XCTAssertFalse(ApiInstant.isScheduledFuture(kickoff, now: now))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIsScheduledOnCalendarHidesYesterday() {
|
||||||
|
let yesterday = "2026-09-05T10:00:00Z"
|
||||||
|
let now = iso("2026-09-06T08:30:00Z")
|
||||||
|
XCTAssertFalse(ApiInstant.isScheduledOnCalendar(yesterday, now: now))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConcurrentStreamForbiddenShowsApiMessage() {
|
||||||
|
let body = """
|
||||||
|
{"error":"Hai già una diretta in corso con questo account. Chiudila prima di avviarne un’altra.","error_code":"user_concurrent_stream"}
|
||||||
|
"""
|
||||||
|
XCTAssertEqual(
|
||||||
|
APIError.http(403, body).errorDescription,
|
||||||
|
"Hai già una diretta in corso con questo account. Chiudila prima di avviarne un’altra."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func iso(_ value: String) -> Date {
|
||||||
|
let f = ISO8601DateFormatter()
|
||||||
|
f.formatOptions = [.withInternetDateTime]
|
||||||
|
guard let date = f.date(from: value) else {
|
||||||
|
XCTFail("Data ISO non valida: \(value)")
|
||||||
|
return Date()
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
func testOverlayKindMapping() {
|
func testOverlayKindMapping() {
|
||||||
XCTAssertEqual(OverlayKindMapping.fromApi("basket"), "basket")
|
XCTAssertEqual(OverlayKindMapping.fromApi("basket"), "basket")
|
||||||
XCTAssertEqual(OverlayKindMapping.fromApi("none"), "none")
|
XCTAssertEqual(OverlayKindMapping.fromApi("none"), "none")
|
||||||
|
|||||||
@@ -121,6 +121,65 @@ final class MatchScoringRulesTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class MatchHubCalendarTests: XCTestCase {
|
||||||
|
func testHubKeepsMatchAfterKickoffSameDay() {
|
||||||
|
let now = iso("2026-09-06T08:30:00Z")
|
||||||
|
let match = hubMatch(scheduledAt: "2026-09-06T07:00:00Z")
|
||||||
|
XCTAssertTrue(MatchHubFilter.coachHubVisible(match, now: now))
|
||||||
|
XCTAssertTrue(MatchHubFilter.isOnCalendar(match, now: now))
|
||||||
|
XCTAssertFalse(MatchHubFilter.isScheduledFuture(match, now: now))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHubHidesYesterdayMatch() {
|
||||||
|
let now = iso("2026-09-06T08:30:00Z")
|
||||||
|
let match = hubMatch(scheduledAt: "2026-09-05T10:00:00Z")
|
||||||
|
XCTAssertFalse(MatchHubFilter.coachHubVisible(match, now: now))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHubRespectsApiCoachHubVisibleFalse() {
|
||||||
|
let now = iso("2026-09-06T08:30:00Z")
|
||||||
|
let match = hubMatch(scheduledAt: "2026-09-06T07:00:00Z", coachHubVisible: false)
|
||||||
|
XCTAssertFalse(MatchHubFilter.coachHubVisible(match, now: now))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func iso(_ value: String) -> Date {
|
||||||
|
let f = ISO8601DateFormatter()
|
||||||
|
f.formatOptions = [.withInternetDateTime]
|
||||||
|
return f.date(from: value)!
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hubMatch(scheduledAt: String?, coachHubVisible: Bool? = nil) -> Match {
|
||||||
|
Match(
|
||||||
|
id: "match-hub",
|
||||||
|
teamId: "team-1",
|
||||||
|
teamName: "Team MLTV",
|
||||||
|
opponentName: "Team Guest",
|
||||||
|
location: nil,
|
||||||
|
scheduledAt: scheduledAt,
|
||||||
|
sportKey: "pallavolo",
|
||||||
|
sportLabel: "Pallavolo",
|
||||||
|
boardType: "volley",
|
||||||
|
overlayKind: "volley",
|
||||||
|
effectiveOverlayKind: "volley",
|
||||||
|
setsToWin: 3,
|
||||||
|
category: nil,
|
||||||
|
scoringRules: nil,
|
||||||
|
activeSessionId: nil,
|
||||||
|
activeSessionStatus: nil,
|
||||||
|
streamCompleted: false,
|
||||||
|
coachHubVisible: coachHubVisible,
|
||||||
|
homePrimaryColor: "#FF2D2D",
|
||||||
|
homeSecondaryColor: nil,
|
||||||
|
homeLogoUrl: nil,
|
||||||
|
opponentPrimaryColor: "#1E3A8A",
|
||||||
|
opponentLogoUrl: nil,
|
||||||
|
effectiveCoverUrl: nil,
|
||||||
|
coverSource: "default",
|
||||||
|
customCoverEnabled: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ScoreboardOverlayTests: XCTestCase {
|
final class ScoreboardOverlayTests: XCTestCase {
|
||||||
func testScoreboardColumnsAfterSetClosed() {
|
func testScoreboardColumnsAfterSetClosed() {
|
||||||
let score = ScoreState(
|
let score = ScoreState(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ lines += [
|
|||||||
app_settings = """
|
app_settings = """
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
@@ -107,7 +107,7 @@ app_settings = """
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
|
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
API_BASE_URL = "https://www.matchlivetv.it";
|
API_BASE_URL = "https://www.matchlivetv.it";
|
||||||
@@ -122,10 +122,10 @@ app_debug_settings = app_settings + """
|
|||||||
test_settings = """
|
test_settings = """
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 37;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv.tests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv.tests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|||||||
Reference in New Issue
Block a user